You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用AEM模型@PostConstruct方法时出现NullPointerException问题排查

AEM模型单元测试空指针异常排查

我尝试用AemContext将JSON资源文件适配到AEM模型,通过@PostConstruct注解初始化模型方法。但运行测试时,调用测试类中的getList()函数总会出现NullPointerException。我正在学习对这类依赖@PostConstruct注入的模型进行单元测试,希望了解出现空指针异常的原因。

模型接口

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

模型实现类

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

模型测试类

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文件

{
    "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"
        }
        }
    }
}

空指针异常原因分析

  1. 模型适配方式错误
    TableModelImpl通过@Model注解声明了adaptables = Resource.class,意味着这个模型只能从Resource对象适配得到。但测试代码中使用aemContext.request().adaptTo(TableModelImpl.class)尝试从Request对象适配模型,这会导致tableModelImpl为null,后续调用getList()自然触发空指针异常。

  2. 预期数据构造逻辑错误(非空指针直接原因,但会导致断言失败)
    测试中复用了同一个map对象,先添加到expectedItemsChildren后又修改其内容,最终预期列表里的两个元素其实是同一个对象,而模型返回的是两个独立的Map对象,会导致断言比对失败。

修复方案

  1. 修正模型适配方式
    将测试中的适配代码改为从当前Resource对象适配:

    tableModelImpl = aemContext.currentResource().adaptTo(TableModelImpl.class);
    
  2. 修正预期数据构造
    为每个预期条目创建独立的Map对象,同时保证内容和JSON文件一致:

    Map<String, String> map1 = new HashMap<>();
    map1.put("title", "TItle");
    map1.put("content","Content");
    expectedItemsChildren.add(map1);
    
    Map<String, String> map2 = new HashMap<>();
    map2.put("title", "Many");
    map2.put("content","More");
    expectedItemsChildren.add(map2);
    
  3. 移除不必要的依赖
    测试类中没有使用Mockito的任何功能,可以去掉MockitoExtension的扩展声明:

    @ExtendWith(AemContextExtension.class)
    

内容的提问来源于stack exchange,提问作者Anim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 19:12:20