Search code examples
javajunitaemslingjcr

NullPointer exception on calling @Postconstruct method in AEM Model


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.