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
相关产品推荐
相关产品推荐

