Issue
I am trying to use AemContext to adapt the JSON resource file with Model. I used @Postconstruct annotation for initializing the model function. But every time I run the project, I get a null pointer exception on invocking getList() function in the test class. I am trying to get a grasp of unit testing this model which runs through @Postconstruct injection. It will be really helpful if I get to know the reason of getting null pointer exception.
Model Interface
package com.xyz.core.models;
import java.util.List;
import java.util.Map;
public interface TableModel {
/**
* Function to be implemented to fetch
* list of table items from JCR
*
* @return list of items
*/
public List<Map<String, String>> getList();
}
Model Class
package com.xyz.core.models.impl;
import com.xyz.core.models.TableModel;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Optional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Model(adaptables = Resource.class, adapters = TableModel.class)
public class TableModelImpl implements TableModel{
@Inject
@Optional
private Resource items;
private List<Map<String, String>> itemsChildren = new ArrayList<Map<String, String>>();
@PostConstruct
public void init(){
if (items != null) {
for (Resource resource: items.getChildren()) {
ValueMap properties = resource.adaptTo(ValueMap.class);
String title = properties.get("title", String.class);
String content = properties.get("content", String.class);
Map<String, String> map = new HashMap<String, String>();
map.put("title", title);
map.put("content", content);
itemsChildren.add(map);
}
}
}
public List<Map<String, String>> getList() {
return Collections.unmodifiableList(itemsChildren);
}
}
Test Class of Model Class
package com.xyz.core.models;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import org.junit.jupiter.api.BeforeEach;
import com.xyz.core.models.impl.TableModelImpl;
import io.wcm.testing.mock.aem.junit5.AemContext;
import io.wcm.testing.mock.aem.junit5.AemContextExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith({AemContextExtension.class, MockitoExtension.class})
public class TableModelImplTest {
private final AemContext aemContext = new AemContext();
TableModelImpl tableModelImpl;
private List<Map<String, String>> expectedItemsChildren = new ArrayList<Map<String, String>>();
private final Map<String, String> map = new HashMap<String, String>();
@BeforeEach
void setUp() {
aemContext.addModelsForClasses(TableModelImpl.class);
aemContext.load().json("/com/xyz/core/models/impl/Table.json", "/component");
}
@Test
void getList() {
aemContext.currentResource("/component/table");
tableModelImpl = aemContext.request().adaptTo(TableModelImpl.class)
map.put("title", "TItle");
map.put("content","content");
this.expectedItemsChildren.add(map);
map.put("title", "Many");
map.put("content","More");
List<Map<String, String>> actualItemsChildren = tableModelImpl.getList();
assertEquals(expectedItemsChildren, actualItemsChildren);
}
}
JSON file from resource
{
"table":{
"jcr:primaryType":"nt:unstructured",
"jcr:createdBy":"admin",
"jcr:lastModifiedBy":"admin",
"jcr:created":"Wed Aug 24 2022 10:38:41 GMT+0900",
"jcr:lastModified":"Wed Aug 24 2022 10:39:03 GMT+0900",
"sling:resourceType":"xyz/components/table",
"items":{
"jcr:primaryType":"nt:unstructured",
"item0":{
"jcr:primaryType":"nt:unstructured",
"title":"TItle",
"content":"Content"
},
"item1":{
"jcr:primaryType":"nt:unstructured",
"title":"Many",
"content":"More"
}
}
}
}
Solution
Your line
tableModelImpl = aemContext.request().adaptTo(TableModelImpl.class)
tries to adapt the sling request to your model.
However, your model declares it’s adaptable from Resource
only (adaptables = Resource.class
).
Try
tableModelImpl = aemContext.currentResource("/component/table").adaptTo(TableModelImpl.class)
instead.
Answered By - Raphael Schweikert
Answer Checked By - Marie Seifert (JavaFixing Admin)