Your achievements

Level 1

0% to

Level 2

Tip /
Sign in

Sign in to Community

to gain points, level up, and earn exciting badges like the new
BedrockMission!

Learn more

View all

Sign in to view all badges

SOLVED

JUnit Test Cases for Sling Models based on Delegation Pattern

gomatis80521865
Level 1
Level 1

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

junit models sling
1 Accepted Solution
Rick_Holdsworth
Correct answer by
Level 2
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

27 Replies
Arun_Patidar
Community Advisor
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.

gomatis80521865
Level 1
Level 1
I tried adding that additional adapter class and I'm still getting null when I try to adapt the context request to my CustomTeaser
gomatis80521865
Level 1
Level 1
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.
gomatis80521865
Level 1
Level 1
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.
pjcalvo
Level 1
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.

GonzaloCalandria
Level 1
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 @Self) null.
Arun_Patidar
Community Advisor
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.
chris-parr
Level 1
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).

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

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);

 

 

 

Jörg_Hoh
Employee
Employee

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

 

Jörg

gomatis80521865
Level 1
Level 1
Yes. I have included it in the context as follows: context.addModelsForClasses(com.adobe.cq.wcm.core.components.models.Teaser.class);
gomatis80521865
Level 1
Level 1
Yes. I have included it in the context as follows:context.addModelsForClasses(com.adobe.cq.wcm.core.components.models.Teaser.class);
amirk63597623
Level 1
Level 1
Hello! Have you resolved the issue? I mean the case when we use one of the core component Teaser's methods? I also have a NullPointer.
robertod4340573
Level 1
Level 1

I have the same issue. I needed to declare the delegate proxy core component (Page in my case) as optional in my sling model as it is not resolved in the unit test (should not be optional in the real use case). Is a pity because my custom logic is simply a fallback of the core component one. I would like to have the object not null in my unit test so I will drop the Optional annotation for the field.