hi folks,
I'm trying to get into using @Models instead of Use classes and Servlets.
I can get the @Models to work o.k. but I'm having trouble with unit tests for them.
I set up a pretend "Resource" with a node with a string property "target" of value "target".
However, I can't map my @Model to it. I just get back "nulls" instead of parameter values.
See Model and Unit Test below.
Any ideas ?
thanks
Fiona
@Model(
// This must adapt from a SlingHttpServletRequest, since this is invoked directly via a request, and not via a resource.
// If can specify Resource.class as a second adaptable as needed
adaptables = { SlingHttpServletRequest.class, Resource.class },
adapters = {RelatedArticleModel.class},
// The resourceType is required if you want Sling to "naturally" expose this models as the exporter for a Resource.
resourceType = RelatedArticleModel.TITLE_RESOURCE_TYPE,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
// name = the registered name of the exporter
// extensions = the extensions this exporter is registered to
// selector = defaults to "models", can override as needed; This is helpful if a single resource needs 2 different JSON renditions
@Exporter(name = "jackson", extensions = "json", options = {
/**
* Jackson options:
* - Mapper Features: http://static.javadoc.io/com.fasterxml.jackson.core/jackson-databind/2.8.5/com/fasterxml/jackson/databind/MapperFeature.html
* - Serialization Features: http://static.javadoc.io/com.fasterxml.jackson.core/jackson-databind/2.8.5/com/fasterxml/jackson/databind/SerializationFeature.html
*/
@ExporterOption(name = "MapperFeature.SORT_PROPERTIES_ALPHABETICALLY", value = "true"),
@ExporterOption(name = "SerializationFeature.WRITE_DATES_AS_TIMESTAMPS", value="false")
})
public class RelatedArticleModel {
public static final String TITLE_RESOURCE_TYPE = "xyz/components/content/relatedarticle";
@Self
private SlingHttpServletRequest request;
@ScriptVariable
private ResourceResolver resolver;
@ValueMapValue(optional = false)
private String title;
@ValueMapValue(optional = false)
private String target;
@PostConstruct
// PostConstructs are called after all the injection has occurred, but before the Model object is returned for use.
private void init() {
// do stuff
}
public String getTitle() {
return title;
}
public String getTarget() {
return target;
}
public void setTitle(String title) {
this.title = title;
}
public void setTarget(String target) {
this.target = target;
}
}
import io.wcm.testing.mock.aem.junit5.AemContext;
import io.wcm.testing.mock.aem.junit5.AemContextExtension;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.testing.mock.sling.ResourceResolverType;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import static junitx.framework.Assert.assertEquals;
@ExtendWith({AemContextExtension.class, MockitoExtension.class})
public class RelatedArticleModelTest {
public final AemContext ctx = new AemContext(ResourceResolverType.JCR_MOCK);
private RelatedArticleModel underTest;
@BeforeEach
void setUp() throws Exception {
ctx.addModelsForClasses(RelatedArticleModel.class);
}
@Test
public void testGetTarget() {
Resource relatedArticleResourceContext = ctx.create().resource("/content/relatedarticle",
"sling:resourceType", "xyz/components/content/relatedarticle",
"target", "target");
System.out.println("resource path is " + relatedArticleResourceContext.getPath());
System.out.println("resource type is " + relatedArticleResourceContext.getResourceType());
System.out.println("resource name is " + relatedArticleResourceContext.getName());
System.out.println("resource target from valuemap is " + relatedArticleResourceContext.getValueMap().get("target", String.class));
underTest = relatedArticleResourceContext.adaptTo(RelatedArticleModel.class);
assertEquals("target", underTest.getTarget()); <<< *****Null pointer exception, underTest.getTarget() is null*****
}
}
Solved! Go to Solution.
Views
Replies
Total Likes
Hi @fionas76543059,
Try to create resource properties as Map object as shown in the official doc. (based on the way you are creating mock resource).
Alternatively you can also try to create mock resource with properties as JSON file and use context.load.json("json path", "sample resource path") - Sample snippet available in the same doc.
https://wcm.io/testing/aem-mock/usage-content-loader-builder.html
Hi @fionas76543059,
Try to create resource properties as Map object as shown in the official doc. (based on the way you are creating mock resource).
Alternatively you can also try to create mock resource with properties as JSON file and use context.load.json("json path", "sample resource path") - Sample snippet available in the same doc.
https://wcm.io/testing/aem-mock/usage-content-loader-builder.html
Hi @fionas76543059,
Did you try the below and still getting NPE ?
We need to create a mock Resource object with properties as your model's sling:resourceType and other properties you are injecting in your model. In this example, it is "target" and "title"
Resource mockResc = context.create().resource("/content/test1", ImmutableMap.<String, Object>builder()
.put("sling:resourceType", "xyz/components/content/relatedarticle")
.put("target", "target").put("title", "Article")
.build());
mockResc.adaptTo(RelatedArticleModel.class);
Possible causes of mock Resource not adapting to your model
I am stuck in same scenario, where I have @PostConstruct method in model and I am initializing resourceResolver in that. This is working fine on actual page.
@PostConstruct public void init(){ resourceResolver = page.getContentResource().getResourceResolver(); }
But while writing Junit for that particular Sling Model, It's giving NPE because of this @PostConstruct method.
Can you help me on this? How to mock this resolver thing in Junits?
Thanks
Cross check if the "page" Object(that you are using to get the resolver) is set/visible in the AemContext.
Alternatively you can inject ResourceResolver via @ScriptVariable Annotation in your actual Sling Model Class.
Thanks!
I tried with a much simpler @Model that only has 2 parameters and no PostConstruct stuff.. The resource is read fine from the file.but after the adaptTo, I just get a Null Pointer exception when I try to access the class parameters.
{
"cta": {
"jcr:primaryType": "nt:unstructured",
"sling:resourceType": "xya/components/content/cta",
"target": "target",
"icon": "image"
}
}
@Model(adaptables = Resource.class)
public class CallToActionModel {
private String icon;
private String target;
public String getTarget() { return target; }
public void setTarget(String target) { this.target = target; }
public String getIcon() { return icon; }
public void setIcon(String icon) { this.icon = icon; }
}
context.load().json("/CTAModelTest.json", "/content");
Resource resource = context.resourceResolver().getResource("/content/cta");
System.out.println("resource path is " + resource.getPath());
System.out.println("resource type is " + resource.getResourceType());
CallToActionModel cta = resource.adaptTo(CallToActionModel.class);
System.out.println("cta is " + cta.toString());
Small change in the JSON content and path. Can you try the below and let know if it works.
JSON file content :
{
"jcr:primaryType": "nt:unstructured",
"sling:resourceType": "xya/components/content/cta",
"target": "target",
"icon": "image"
}
Code snippet: (Path to which we load the JSON and the path that we set the context with is same)
private final String CONTENT_PATH = "/content/cta";
private final String MOCK_RESC_JSON = "/CTAModelTest.json"; (this json is available under src/test/resources)
aemContext.load().json(MOCK_RESC_JSON, CONTENT_PATH);
Resource resource = aemContext.currentResource(CONTENT_PATH);
CallToActionModel cta = resource.adaptTo(CallToActionModel.class);
Thanks Vijayalakschmi!
That seem to do the trick!
I also added
context.addModelsForClasses(CallToActionModel.class);
and I put @ValueMapValue() annotations on the parameters in the model also.