Expand my Community achievements bar.

Don’t miss the AEM Skill Exchange in SF on Nov 14—hear from industry leaders, learn best practices, and enhance your AEM strategy with practical tips.
SOLVED

JUnit Test Cases for Sling Models based on Delegation Pattern

Avatar

Level 2

I am trying to write jUnit test cases for custom teaser component which extends the core Teaser component using the Delegation Pattern for Sling Models using "@Self @Via(type=ResourceSuperType.class)" as specified here: https://github.com/adobe/aem-core-wcm-components/wiki/Delegation-Pattern-for-Sling-Models
When I try to set the context (io.wcm.testing.mock.aem.junit5.AemContext) and adapt the context's request (I've tried resource as well) to the model I have created (like the "PageHeadline" from the example), I am getting a NullPointerException.

I am using AEM 6.5.2.0 and trying to run test cases in JUnit 5

1 Accepted Solution

Avatar

Correct answer by
Level 2

I've managed to find a solution.

 

Basically instead of relying on injection during the Unit test I've used Mockito and FieldSetter.setField

 

The only change to the source code would be to ensure the defaultInjectionStrategy was Optional. Not ideal but minimal

 

First we need to adapt the request to core Teaser.class

Teaser coreTeaser = request.adaptTo(Teaser.class);

Then we adapt the request to our custom teaser

CustomTeaser underTest = request.adaptTo(CustomTeaser.class);

Then we set the field that's not injecting

try {
FieldSetter.setField(underTest, underTest.getClass().getDeclaredField("teaser"), coreTeaser);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}

 

Specific to Teaser I also had to set some SlingBindings so that @ScriptVariables were set when adapting the request to Teaser.class

@BeforeEach

request = context.request();

Component component = mock(Component.class);
Style style = mock(Style.class);
SlingBindings slingBindings = (SlingBindings) request.getAttribute(SlingBindings.class.getName());
slingBindings.put(WCMBindingsConstants.NAME_COMPONENT, component);
slingBindings.put(WCMBindingsConstants.NAME_CURRENT_STYLE, style);
request.setAttribute(SlingBindings.class.getName(), slingBindings);

 

Following may only be specific to our POM setup but thought I'd add for completeness 

 

I also had to bump aem mock junit5 dependency to

<artifactId>io.wcm.testing.aem-mock.junit5</artifactId>
<version>4.0.0</version>

 

View solution in original post

36 Replies

Avatar

Community Advisor

Hi,

You need to use one more adaptable to adapt Teaser Component 

e.g.

@Model(adaptables = SlingHttpServletRequest.class, adapters = {Teaser.class, CustomTeaser.class}, resourceType = TeaserImpl.RESOURCE_TYPE)

 

Use CustomeTaeser to adapt to Junit test cases.

 

It will not cover 100% coverage but you will be able to write test cases for custom functionality.



Arun Patidar

Avatar

Level 2
I tried adding that additional adapter class and I'm still getting null when I try to adapt the context request to my CustomTeaser

Avatar

Level 2
Thank you Arun! I resolved the issue with the help of your sample code. I had missed out the "defaultInjectionStrategy=DefaultInjectionStrategy.OPTIONAL" option in my custom model which in turn caused an error during init when being initialised through the test case.

Avatar

Level 2
Hi Arun. Is it possible to create JUnit test cases even if we use one of the core component Teaser's methods (say like isActionsEnabled or getActions) inside our CustomTeaser's @PostConstruct method? Because when I try to do that I am not able adapt the context without encountering a NullPointer inside the method annotated with @PostConstruct in CustomTeaser.

Avatar

Level 1

Hi. I am having the same problem here. The core component property is always null in the custom implementation. And making it Optional just skips the instantiation of it.

Avatar

Level 1
Have you fixed the null Teaser object in your test when you did adaptTo? I'm trying to do the same with an extesion of the Image core component but I'm still having my image field (the one with the @Deleted Account) null.

Avatar

Community Advisor
Hi,I am also not able to get all the properties of Teaser in junit test class, I was able to manage to write test cases only for custom properties using CustomTeaser class adapter. If you will adapt model to Teaser, you will see NPE.


Arun Patidar

Avatar

Level 5

hi @arunpatidar - were you able to properties of Teaser in junit test class,

Avatar

Level 1

I don't see the accepted solution as a solution. The model fails to adapt.

Making the field in the model optional hides the issue, but this too is not a solution (it just masks the problem).

Avatar

Level 2
I also do not see this as an acceptable answer. My Teaser via the ResourceSuperType is always null

Hi Arun_PatidarEven I am facing a similar issue, can below is my component model

@Model(adaptables = SlingHttpServletRequest.class, adapters = {List.class, ComponentExporter.class, TeaserListModel.class}, resourceType = TeaserListModel.TEASER_LIST_RESOURCE_TYPE,defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)

public class TeaserListModel implements List {

public static final String TEASER_LIST_RESOURCE_TYPE = "/components/template/teaserlist";

/*implementation*/

}

here is my Junit Test Case :

public abstract class AbstractTest {

public final void setUpTest() {
MockitoAnnotations.openMocks(this);

this.resolver = aemContext.resourceResolver();
this.request = aemContext.request();
this.response = aemContext.response();
this.pageManager = aemContext.pageManager();

String contentFilePath = this.getContentFilePath();
if (StringUtils.isNotEmpty(contentFilePath)) {
this.aemContext.load().json(contentFilePath, "/content");
}

}



class TeaserListModelTest extends AbstractTest {

@Override
protected String getContentFilePath() {
//loading the json here
return "/models/template/TeaserListModelTest.json";
}

@test
void testTeaserListModelValues() {

//aemContext is the context
Resource myResource = aemContext.resourceResolver().getResource("/TeaserList");
TeaserListModel model = myResource.adaptTo(TeaserListModel.class);
assertTrue(model.linkItems());
assertTrue(model.showDescription());
assertTrue(model.showModificationDate());

}
}

TeaserListModelTest.json file

{
"TeaserList": {
"jcr:primaryType": "nt:unstructured",
"showModificationDate": "true",
"jcr:createdBy": "admin",
"tagsMatch": "any",
"linkItems": "true",
"orderBy": "title",
"jcr:lastModifiedBy": "admin",
"jcr:created": "Mon Jul 12 2021 10:40:37 GMT-0400",
"sortOrder": "asc",
"id": "567",
"maxItems": "4",
"showDescription": "true",
"jcr:lastModified": "Mon Jul 12 2021 10:41:05 GMT-0400",
"childDepth": "4",
"listFrom": "children"
}
}


I am getting a NullPointer at TeaserListModel model = myResource.adaptTo(TeaserListModel.class);

 

 

 

Avatar

Community Advisor

can you check if resource exists?

Resource myResource = aemContext.resourceResolver().getResource("/TeaserList");

 

In json the resource type is missing as well.



Arun Patidar

Avatar

Employee Advisor

have you registered the Sling model for the core teaser component as well to the context?

 

Jörg

Avatar

Level 2
Yes. I have included it in the context as follows: context.addModelsForClasses(com.adobe.cq.wcm.core.components.models.Teaser.class);

Avatar

Level 2
Yes. I have included it in the context as follows:context.addModelsForClasses(com.adobe.cq.wcm.core.components.models.Teaser.class);