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

JUnit测试覆盖率仅50%:JSONObject相关代码块未覆盖求助

AEM资产上传服务测试覆盖率问题排查

问题背景

实现了AEM资产上传的Java服务类AssetUploadAPIServiceImpl,对应的JUnit测试用例执行后代码覆盖率仅为50%,JSONObject解析相关代码块未被覆盖,调整测试类后覆盖率仍未提升。

实现类代码(AssetUploadAPIServiceImpl)

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.json.JSONObject;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.AssetManager;

@Component(service = AssetUploadAPIService.class)
public class AssetUploadAPIServiceImpl implements AssetUploadAPIService {

    @Reference
    transient ResourceResolverFactory resolverFactory;

    private transient Map<String, Object> param = new HashMap<>();

    private static final Logger LOGGER = LoggerFactory.getLogger(AssetUploadAPIServiceImpl.class);
    
    public AssetUploadAPIServiceImpl() {
         param.put(ResourceResolverFactory.SUBSERVICE, "datawrite");
    }

    private ResourceResolver getResourceResolver() throws LoginException {
        return resolverFactory.getServiceResourceResolver(param);
    }

    @Override
    public ServletResponse<String> getAssetUploadResponse(SlingHttpServletRequest request) {
        try (ResourceResolver resourceResolver = getResourceResolver()) {
            StringBuilder sb = new StringBuilder();
            BufferedReader reader = request.getReader();
            try {
                String line = null;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
            } finally {
                reader.close();
            }

            LOGGER.info("POST JSON data: {}", sb);
            JSONObject jsonObject = new JSONObject(sb.toString());
            String assetPath = jsonObject.getString("assetPath");
            String mimeType = jsonObject.getString("mimeType");
            String encoding = jsonObject.getString("encoding");
            String asset = jsonObject.getString("asset");
            String imageType = mimeType.split("/")[1];

            LOGGER.info("assetPath: {}, mimeType: {}, encoding: {}, asset: {}, imageType: {}", assetPath, mimeType,encoding, asset, imageType);

            InputStream targetStream = new ByteArrayInputStream(Base64.getDecoder().decode(asset.getBytes(StandardCharsets.UTF_8)));

            AssetManager assetManager = resourceResolver.adaptTo(AssetManager.class);
            Asset imageAsset = assetManager.createAsset((assetPath + "." + imageType), targetStream, mimeType, true);
            return new ServletResponse<>(ServletResponse.ServletResponseType.RES_200, imageAsset.getPath());
        } 
        catch (Exception e) {
            LOGGER.error(String.format("There was an Error  === " + e));
            return new ServletResponse<>(ServletResponse.ServletResponseType.RES_400, e.getMessage());
        }
    }

}

测试类代码(AssetUploadAPIServiceImplTest)

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.HashMap;

import javax.jcr.Session;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
import org.apache.sling.testing.mock.sling.ResourceResolverType;
import org.json.JSONException;
import org.junit.Rule;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import com.day.cq.wcm.foundation.forms.FormsHandlingRequest;

import io.wcm.testing.mock.aem.junit5.AemContext;

class AssetUploadAPIServiceImplTest {

    @Mock
    private ResourceResolverFactory resolverFactory;

    @Mock
    private Session session;

    @Mock
    private ResourceResolver resourceResolver;

    @Rule
    private final AemContext context = new AemContext(ResourceResolverType.JCR_MOCK);

    @InjectMocks
    private AssetUploadAPIServiceImpl assetUploadAPIService = new AssetUploadAPIServiceImpl();

    @BeforeEach
    public void setup() {
        MockitoAnnotations.initMocks(this);
        HashMap<String, Object> parameterMap = new HashMap<>();
        parameterMap.put("assetPath", "/content/dam/public/assets");
        parameterMap.put("mimeType", "image/png");
        parameterMap.put("encoding", "base64");
        context.request().setParameterMap(parameterMap);
    }

    @Test
    void getAssetUploadResponse() throws LoginException, JSONException {
        when(resolverFactory.getServiceResourceResolver(any())).thenReturn(resourceResolver);
        when(resourceResolver.adaptTo(Session.class)).thenReturn(session);
        ServletResponse<String> actualAssetUploadResponse = assetUploadAPIService.getAssetUploadResponse(new FormsHandlingRequest(new SlingHttpServletRequestWrapper(mock(SlingHttpServletRequest.class))));
        assertNull(actualAssetUploadResponse.getPayload());
        assertEquals(ServletResponse.ServletResponseType.RES_400, actualAssetUploadResponse.getStatus());
    }

}

未覆盖代码块

JSONObject jsonObject = new JSONObject(sb.toString());
String assetPath = jsonObject.getString("assetPath");
String mimeType = jsonObject.getString("mimeType");
String encoding = jsonObject.getString("encoding");
String asset = jsonObject.getString("asset");
String imageType = mimeType.split("/")[1];

排查建议

  • 请求体模拟错误:实现类通过request.getReader()读取JSON请求体,但当前测试仅设置了请求参数,未MockgetReader()返回有效JSON内容,导致代码直接进入异常分支。需MockgetReader()返回包含完整测试JSON的BufferedReader。
  • 请求包装类冗余:测试中嵌套使用FormsHandlingRequest和SlingHttpServletRequestWrapper,可能导致Mock的getReader()失效。直接MockSlingHttpServletRequest并设置方法返回值更可靠。
  • 缺少正常流程测试:当前测试仅覆盖了异常分支(返回400),未编写正常流程的测试用例。需新增测试方法,模拟完整JSON请求体、正确的ResourceResolver和AssetManager行为,验证200分支的执行。
  • Mock逻辑不完整:未MockAssetManager的createAsset()方法,即使请求体正确,后续也会抛出异常进入400分支。需MockresourceResolver.adaptTo(AssetManager.class)返回Mock实例,并设置createAsset()返回Mock的Asset。

具体调整示例

修改测试方法,正确Mock请求体与依赖:

@Test
void getAssetUploadResponse_Success() throws Exception {
    // 构造测试用JSON字符串
    String testJson = "{\"assetPath\": \"/content/dam/public/assets\", \"mimeType\": \"image/png\", \"encoding\": \"base64\", \"asset\": \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==\"}";
    BufferedReader mockReader = new BufferedReader(new StringReader(testJson));
    
    // Mock请求的getReader方法
    SlingHttpServletRequest mockRequest = mock(SlingHttpServletRequest.class);
    when(mockRequest.getReader()).thenReturn(mockReader);
    
    // Mock ResourceResolver和AssetManager
    AssetManager mockAssetManager = mock(AssetManager.class);
    Asset mockAsset = mock(Asset.class);
    when(resolverFactory.getServiceResourceResolver(any())).thenReturn(resourceResolver);
    when(resourceResolver.adaptTo(AssetManager.class)).thenReturn(mockAssetManager);
    when(mockAssetManager.createAsset(anyString(), any(InputStream.class), anyString(), anyBoolean())).thenReturn(mockAsset);
    when(mockAsset.getPath()).thenReturn("/content/dam/public/assets.png");
    
    // 执行测试
    ServletResponse<String> response = assetUploadAPIService.getAssetUploadResponse(mockRequest);
    
    // 验证结果
    assertEquals(ServletResponse.ServletResponseType.RES_200, response.getStatus());
    assertEquals("/content/dam/public/assets.png", response.getPayload());
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 15:20:54