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

ApiUtility.isPublished方法JUnit测试空指针异常排查求助

解决JUnit测试中的NullPointerException问题

问题根源分析

空指针出现在ApiUtility.isPublished方法的resource.getChild(Constants.JCR_CONTENT_NODE)行,核心原因:

  • Servlet调用该方法时传入的路径对应的资源在测试上下文不存在,导致resourceResolver.getResource(path)返回null,后续调用resource.getChild()触发空指针。
  • 测试代码未设置request.getPathInfo()的值,而Servlet中调用isPublished依赖的是这个请求路径,不是参数里的PATH参数,路径不匹配。
  • 测试类混用JUnit 4的@Rule和JUnit 5的@ExtendWith,可能导致上下文初始化异常。

修复步骤

1. 增强ApiUtility.isPublished方法的健壮性(推荐)

在调用resource.getChild()前先判断resource是否为null,从根源避免空指针:

public static boolean isPublished(ResourceResolver resourceResolver, String path) {
   logger.info("Inside isPublished");
   Resource resource = resourceResolver.getResource(path);
   logger.info("Inside isPublished resource ===== "+ resource);
   // 先判断资源是否存在
   if (resource == null) {
      return false;
   }
   Resource jcrResource = resource.getChild(Constants.JCR_CONTENT_NODE);
   if (jcrResource != null) {
      return jcrResource.getValueMap().get(Constants.CQ_LAST_REPLICATION_ACTION, "").equalsIgnoreCase(Constants.ACTIVATE);
   }
   return false;
}

2. 修改JUnit测试代码,匹配路径并创建测试资源

核心修改点:

  • 去掉JUnit 4的@Rule注解,适配JUnit 5的上下文扩展方式。
  • 在测试初始化时,在AemContext中创建目标路径的资源及jcr:content节点,设置复制状态属性。
  • 设置request.getPathInfo()的值为预期路径,保证Servlet调用isPublished时路径匹配。

修改后的完整测试类代码:

import static org.mockito.Mockito.when;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

import javax.jcr.RepositoryException;

import org.apache.sling.api.resource.LoginException;
import org.apache.sling.testing.mock.sling.ResourceResolverType;
import org.junit.Assert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import com.google.gson.Gson;

import ai.test.core.TestUtils;
import ai.test.core.constants.Constants;
import ai.test.core.service.ContentFragmentService;
import ai.test.core.service.EntityRelationUpdateServiceImpl;
import ai.test.core.service.ServletResponse;
import io.wcm.testing.mock.aem.junit5.AemContext;
import io.wcm.testing.mock.aem.junit5.AemContextExtension;

@ExtendWith({AemContextExtension.class, MockitoExtension.class})
public class AgenciesCommunityNodeAmangerServletTest {
    private static final Gson gson = new Gson();

    // 移除JUnit 4的@Rule注解,直接初始化AemContext
    private final AemContext context = new AemContext(ResourceResolverType.JCR_MOCK);

    @Mock
    ContentFragmentService agenciesService;
    
    @Mock
    EntityRelationUpdateServiceImpl updateService;

    @InjectMocks
    private AgenciesCommunityNodeAmangerServlet agenciesCommunityNodeAmangerServlet = new AgenciesCommunityNodeAmangerServlet();

    private final String COMMUNITY_PATH = "/content/dam/content-admin/global/communities/agency/test";
    
    @BeforeEach
    void setUp() throws RepositoryException, URISyntaxException {
        // 在模拟JCR中创建目标资源
        context.create().resource(COMMUNITY_PATH);
        // 创建jcr:content节点并设置激活状态属性
        Map<String, Object> jcrContentProps = new HashMap<>();
        jcrContentProps.put(Constants.CQ_LAST_REPLICATION_ACTION, Constants.ACTIVATE);
        context.create().resource(COMMUNITY_PATH + "/" + Constants.JCR_CONTENT_NODE, jcrContentProps);
        
        // 设置请求路径,匹配Servlet调用isPublished时的参数
        context.request().setPathInfo(COMMUNITY_PATH);
        
        String json = gson.toJson(TestUtils.getMockBRGCommunityModel(COMMUNITY_PATH));
        context.request().setContent(json.getBytes());
    }

    @Test
    void doPostUpdateRelationShouldReturn200() throws IOException, LoginException {
        when(updateService.updateEntityData(context.request(), COMMUNITY_PATH, Constants.COMMUNITIES))
            .thenReturn(TestUtils.getExpectedServletResponse(ServletResponse.ServletResponseType.RES_200));
        
        Map<String, Object> params = new HashMap<>();
        params.put(Constants.PATH, COMMUNITY_PATH);
        params.put(Constants.TYPE, Constants.VERIFY);
        context.request().setParameterMap(params);
        
        agenciesCommunityNodeAmangerServlet.doPost(context.request(), context.response());

        Assert.assertEquals("Response code should match", 200, context.response().getStatus());
    }
    
    @Test
    void doPostUpdateRelationShouldThrowExceptionReturn500() throws IOException, LoginException {
        when(updateService.updateEntityData(context.request(), COMMUNITY_PATH, Constants.COMMUNITIES))
            .thenReturn(TestUtils.getExpectedServletResponse(ServletResponse.ServletResponseType.RES_500));
        
        Map<String, Object> params = new HashMap<>();
        params.put(Constants.PATH, COMMUNITY_PATH);
        params.put(Constants.TYPE, Constants.VERIFY);
        context.request().setParameterMap(params);
        
        agenciesCommunityNodeAmangerServlet.doPost(context.request(), context.response());

        Assert.assertEquals("Response code should match", 500, context.response().getStatus());
    }

    // 可选:添加未发布状态的测试场景
    @Test
    void doPostWithUnpublishedContentShouldSkipUpdate() throws IOException, LoginException {
        // 修改jcr:content的复制状态为非激活
        context.resourceResolver().delete(context.resourceResolver().getResource(COMMUNITY_PATH + "/" + Constants.JCR_CONTENT_NODE));
        Map<String, Object> jcrContentProps = new HashMap<>();
        jcrContentProps.put(Constants.CQ_LAST_REPLICATION_ACTION, "Deactivate");
        context.create().resource(COMMUNITY_PATH + "/" + Constants.JCR_CONTENT_NODE, jcrContentProps);
        
        Map<String, Object> params = new HashMap<>();
        params.put(Constants.PATH, COMMUNITY_PATH);
        params.put(Constants.TYPE, Constants.VERIFY);
        context.request().setParameterMap(params);
        
        agenciesCommunityNodeAmangerServlet.doPost(context.request(), context.response());

        Assert.assertEquals("Response code should be 200 (no update action)", 200, context.response().getStatus());
    }
}

3. 关键说明

  • 使用context.create().resource()在模拟JCR仓库中创建资源,确保resourceResolver.getResource()能获取到非null的实例。
  • 设置request.setPathInfo(COMMUNITY_PATH)后,Servlet调用isPublished时的路径才会与测试资源路径匹配。
  • 新增的未发布状态测试可以覆盖更多业务场景,保证Servlet逻辑的完整性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 11:29:58