AEM @Model newbie - junit tests - @Model doesn't adapt to fake "Resource" | Community
Skip to main content
Level 6
July 28, 2021
Solved

AEM @Model newbie - junit tests - @Model doesn't adapt to fake "Resource"

  • July 28, 2021
  • 1 reply
  • 8137 views
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*****


}

}




This post is no longer active and is closed to new replies. Need help? Start a new post to ask your question.
Best answer by Vijayalakshmi_S

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

 

1 reply

Vijayalakshmi_S
Vijayalakshmi_SAccepted solution
Level 10
July 28, 2021

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

 

Vijayalakshmi_S
Level 10
July 28, 2021

@fionas76543059

Possible causes of mock Resource not adapting to your model

  • All the mandatory properties that are injected should be available in the mock Resource. 
  • If you have any logic in @PostConstruct method and if it is not visible in the AemContext object, then it will prevent the Model Instantiation (that is adapting mock resource to your Model.class)
    • Example : If we use UserManager API in the model, then the same needs to be set in the AemContext as Mock Object before we could instantiate the Model. (it is like preparing the AemContext with what we use in our Model. Otherwise that API will be null and hence the ultimate model object is null)
Vijayalakshmi_S
Level 10
August 9, 2022

Hi @vijayalakshmi_s 

 

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 🙂


@iamnjain 

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.