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

使用MockMvc无法捕获API抛出的GeneralException异常问题

问题:Mockito测试中无法捕获GeneralException的异常响应
  • 正向测试用例运行正常,但API抛出GeneralException(来自org.zalando.problem依赖)时,MockMvc无法正确捕获异常响应,反而抛出NestedServletException
  • 典型报错:
    org.springframework.web.util.NestedServletException: Request processing failed; nested exception is about:blank{400, Bad Request, Invalid Parameter, message=error.http.400}
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
    
  • 模拟500错误时也会出现类似报错:
    org.springframework.web.util.NestedServletException: Request processing failed; nested exception is about:blank{500, Internal Server Error, message=error.codeException}
    

相关代码

控制器代码

@PostMapping("/eim/metadata/featureData")
public ResponseEntity<?> saveFeatureData(@RequestBody DcpDTO dcp) {

    String errMsg = ResourceUtil.validateRequest(dcp);
    if (StringUtils.isNotBlank(errMsg)) {
        throw new BadRequestAlertException(errMsg);
    }
    if (! this.dcpCommitService.getDcpByName(dcp.getDcpName()).isEmpty()) {
        throw new BadRequestAlertException(String.format("Dcp with name [%s] already exists. Name should be unique.", dcp.getDcpName()));
    }

    DcpJobData featureData = null;
    try
    {
        featureData = dcpCommitService.savefeatureData(dcp);
        dcpCommitService.triggerSparkJob(featureData);
    }
    catch (IllegalArgumentException ex)
    {
        log.warn("savefeatureData - Failed to create resource. {}",ex.getMessage());
        throw new BadRequestAlertException(String.format("Failed to create resource. {%s}", ex.getMessage()));
    } catch (Exception ex) {
        log.error("savefeatureData - Unexpected error. ", ex);
        throw new GeneralException(String.format("Create operation failed. %s", ex.getMessage()));
    }
    return new ResponseEntity<>(featureData, HttpStatus.CREATED);
}

JUnit测试代码

@RunWith(SpringJUnit4ClassRunner.class)
public class ChamberResourceTest {
   
    @InjectMocks
    ChamberResource chamberResource;

    @Mock
    ToolService toolService;

    @Mock
    ChamberService chamberService;

    private MockMvc mockMvc;

    private ToolList toolList;

    private SiteDetail siteDetail;
    private CustomerDetail customerDetail;
    private ChamberList chamberList;

    @Before
    public void initTest() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(chamberResource).build();

    }

    @Test
    public void test_BadRequest() throws Exception {
        when(chamberService.getChamberListByTool(anyList())).thenThrow(BadRequestAlertException.class);
        DcpDTO dcpDTO = new DcpDTO();
        dcpDTO.setCustomerUUID(UUID.fromString("9e94a351-b2b8-4c36-8a9a-6828f1e9932f"));
        dcpDTO.setFleetUUID(UUID.fromString("c69a82c6-55bf-437a-b265-f519aafebf15"));
        dcpDTO.setChamberUUIDs(Arrays.asList("dadc3fd7-90a4-4fc1-9813-b2d99e27bc2b", "fcdf0f3c-6874-480f-9f18-107177176fa6"));
        dcpDTO.setStartTime(LocalDateTime.parse("2022-06-30T17:23:22"));
        dcpDTO.setEndTime(LocalDateTime.parse("2022-06-30T16:42:22"));
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        String jsonString = mapper.writeValueAsString(dcpDTO);
        restUserMockMvc.perform(MockMvcRequestBuilders.post("/eim/metadata/featureData")
                .header(HttpHeaders.AUTHORIZATION, "Basic cmFpYXY6UGFydGhAMDY=")
                .accept(MediaType.APPLICATION_JSON)
                .contentType(MediaType.APPLICATION_JSON)
                .content(jsonString))
            .andDo(print())
            .andExpect(status().isBadRequest());
    }
}

原因分析

  1. MockMvc初始化不完整:使用standaloneSetup时,没有加载项目中处理org.zalando.problem异常的@ControllerAdvice或异常处理器,导致异常无法被转换为标准HTTP响应。
  2. 测试Mock匹配错误:测试中模拟的是chamberService.getChamberListByTool抛出异常,但控制器实际调用的是dcpCommitService的方法,两者不匹配,无法触发预期的异常分支。
  3. 异常未被正确转换:GeneralException需要通过zalando的Problem处理机制转换为HTTP响应,而测试环境中缺少该机制的配置。

解决方案

1. 修复测试Mock匹配问题

确保模拟的方法是控制器实际调用的方法,比如要触发GeneralException,需模拟dcpCommitService的方法抛出异常:

// 示例:模拟savefeatureData抛出异常,触发GeneralException分支
when(dcpCommitService.savefeatureData(any(DcpDTO.class))).thenThrow(new RuntimeException("test error"));

2. 在MockMvc中注册异常处理器

如果项目中有自定义的Problem异常处理类(比如带@ControllerAdvice的类),在初始化MockMvc时手动注册:

@Autowired
private ProblemExceptionHandler problemExceptionHandler; // 项目中的异常处理类

@Before
public void initTest() {
    this.mockMvc = MockMvcBuilders.standaloneSetup(chamberResource)
            .setControllerAdvice(problemExceptionHandler)
            .build();
}

3. 使用webAppContextSetup替代standaloneSetup(推荐)

加载完整的Spring上下文,自动包含所有异常处理配置:

@Autowired
private WebApplicationContext webApplicationContext;

@Before
public void initTest() {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
            .build();
}

4. 直接捕获NestedServletException验证内部异常

若无法加载完整上下文,可在测试中捕获外层异常并验证内部的GeneralException:

@Test
public void test_GeneralException() throws Exception {
    // 模拟触发GeneralException的条件
    when(dcpCommitService.savefeatureData(any(DcpDTO.class))).thenThrow(new RuntimeException("test"));

    try {
        mockMvc.perform(MockMvcRequestBuilders.post("/eim/metadata/featureData")
                .header(HttpHeaders.AUTHORIZATION, "Basic cmFpYXY6UGFydGhAMDY=")
                .accept(MediaType.APPLICATION_JSON)
                .contentType(MediaType.APPLICATION_JSON)
                .content(jsonString))
                .andDo(print());
        fail("Expected NestedServletException");
    } catch (NestedServletException e) {
        // 验证内部异常类型和消息
        assertTrue(e.getCause() instanceof GeneralException);
        assertEquals("Create operation failed. test", e.getCause().getMessage());
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 12:10:33