Common cause for NullPionterException or Null value in Junit test case for sling model in aem

 

Below are the common cause for NullPointerException and Null value in Junit test for Sling model:

  1. Problem with the adaptables
Let say your sling model is adaptables to sling Resource and in your test class you are trying to get the instance of your respective Object by adapting request of aemContext. In this case you will end up with NullPointerException. 
For example:

Sling Model :


@Model(
adaptables = {Resource.class},
adapters = {SolutionHeroComponent.class},
resourceType = {SolutionHeroComponentImpl.RESOURCE_TYPE},
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(
name = "jackson",
selector = "solutionhero",
extensions = "json")
public class SolutionHeroComponentImpl implements SolutionHeroComponent {
protected static final String RESOURCE_TYPE = "myproject/components/solutionhero";
@Inject
@Named(SOLUTION_HERO_TITLE)
private String title;
@PostConstruct
protected void init(){
}

@Override
public String getTitle() {
return title;
}
}


Test :


@ExtendWith({ AemContextExtension.class, MockitoExtension.class })
public class SolutionHeroComponentImplTest {

private final AemContext aemContext = new AemContext();
SolutionHeroComponent solutionHeroComponent;

@BeforeEach
void setUp() {
aemContext.addModelsForClasses(SolutionHeroComponentImpl.class);
aemContext.load().json(
"/com/myproject/aem/models/impl/SolutionHeroComponentImplTest.json", "/content/myproject");
aemContext.currentResource("/content/myproject/solutionhero");
solutionHeroComponent = aemContext.request().adaptTo(SolutionHeroComponent.class);
}

@Test
public void testGetTitle() {
final String expected = "Solution Hero Title";
String actual = solutionHeroComponent.getTitle();
assertEquals(expected, actual);

}
}

Above sling model is adaptables to Resource  when I try adapt it thru request of aemContext in test class it will give a NullPointerException on test run. To avoid such exception you have two options:

    a). Update your sling model to make it adaptables to SlingHttpServeletRequest and get the injected properties thru via annotation, as it is done in below snippet

Updated Sling model:


@Model(
adaptables = {SlingHttpServletRequest.class},
adapters = {SolutionHeroComponent.class},
resourceType = {SolutionHeroComponentImpl.RESOURCE_TYPE},
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(
name = "jackson",
selector = "solutionhero",
extensions = "json")
public class SolutionHeroComponentImpl implements SolutionHeroComponent {
protected static final String RESOURCE_TYPE = "myproject/components/solutionhero";
@Inject
@Named(SOLUTION_HERO_TITLE)
@Via("resource")
private String title;

@PostConstruct
protected void init(){
}

@Override
public String getTitle() {
return title;
}
}



      b). Or try to adapt your Object instance thru currentResource of aemContext in your test case, as in below snippet.

Updated Test case:


@ExtendWith({ AemContextExtension.class, MockitoExtension.class })
public class SolutionHeroComponentImplTest {

private final AemContext aemContext = new AemContext();
SolutionHeroComponent solutionHeroComponent;
Resource jsonResource;

@BeforeEach
void setUp() {
aemContext.addModelsForClasses(SolutionHeroComponentImpl.class);
aemContext.load().json(
"/com/myproject/aem/models/impl/SolutionHeroComponentImplTest.json", "/content/myproject");
jsonResource = aemContext.currentResource("/content/myproject/solutionhero");
solutionHeroComponent = jsonResource.adaptTo(SolutionHeroComponent.class);
}

@Test
public void testGetTitle() {
final String expected = "Solution Hero Title";
String actual = solutionHeroComponent.getTitle();
assertEquals(expected, actual);

}
}  



    2.  Fields name mismatch specially with @Named annotation
Since unite tests are executed outside the context of running  AEM instance so there is no context available to facilitate this, AEM Mocks creates a mock context with the loaded JSON as if they were provided by a real repo. So think of a scenario when you named some of the fields name different than its actual properties name in sling model with @Named annotation.

Actual Properties:


Name of the filed in sling model:


@Model(
adaptables = {Resource.class},
adapters = {SolutionHeroComponent.class},
resourceType = {SolutionHeroComponentImpl.RESOURCE_TYPE},
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(
name = "jackson",
selector = "solutionhero",
extensions = "json")
public class SolutionHeroComponentImpl implements SolutionHeroComponent {
protected static final String RESOURCE_TYPE = "myproject/components/solutionhero";
@Inject
@Named(SOLUTION_HERO_TITLE)
private String title;
@Override
public String getTitle() {
return title;
}
}


In above image actual property name is "solutionHeroTitle"  and field name in sling model is "title"
So what will be name of those fields in resource json, actual or virtual? (Confusion starts here)



okay....

Primafacie, people think name of the filed in resource json will be virtual one because we are testing it from the sling model implementation, where getter is available for virtual name and name of the fields are mapped with the @Named annotation as in above snippet :

Resource JSON:

{
"solutionhero": {
"jcr:primaryType": "nt:unstructured",
"jcr:createdBy": "admin",
"jcr:lastModifiedBy": "admin",
"title": "Solution Hero Title",
"sling:resourceType": "myproject/components/solutionhero"
}
}

Test case:


@ExtendWith({ AemContextExtension.class, MockitoExtension.class })
public class SolutionHeroComponentImplTest {

private final AemContext aemContext = new AemContext();
SolutionHeroComponent solutionHeroComponent;

@BeforeEach
void setUp() {
aemContext.addModelsForClasses(SolutionHeroComponentImpl.class);
aemContext.load().json(
"/com/myproject/aem/models/impl/SolutionHeroComponentImplTest.json", "/content/myproject");
aemContext.currentResource("/content/myproject/solutionhero");
solutionHeroComponent = aemContext.request().adaptTo(SolutionHeroComponent.class);
}

@Test
public void testGetTitle() {
final String expected = "Solution Hero Title";
String actual = solutionHeroComponent.getTitle();
assertEquals(expected, actual);

}
}


when you run the test, it will fail with :


[ERROR] SolutionHeroComponentImplTest.testGetTitle:43
expected: <Solution Hero Title> but was: <null>


so solution to the confusion is, name of the field should be the actual name of the field in the resource json because behind the scene also sling model picks the actual name only. So to make it work you need to just update the above json as below:

{
"solutionhero": {
"jcr:primaryType": "nt:unstructured",
"jcr:createdBy": "admin",
"jcr:lastModifiedBy": "admin",
"solutionHeroTitle": "Solution Hero Title",
"sling:resourceType": "myproject/components/solutionhero"
}
}




There may be other causes also whenever I encounter any such, surely will update the post.


Thank You


Comments

Popular posts from this blog

@Inject : A discouraged sling-model annotation

"java.lang.NoSuchFieldError: FACTORY" while running Junit test in AEM codebase