使用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()); } }
原因分析
- MockMvc初始化不完整:使用
standaloneSetup时,没有加载项目中处理org.zalando.problem异常的@ControllerAdvice或异常处理器,导致异常无法被转换为标准HTTP响应。 - 测试Mock匹配错误:测试中模拟的是
chamberService.getChamberListByTool抛出异常,但控制器实际调用的是dcpCommitService的方法,两者不匹配,无法触发预期的异常分支。 - 异常未被正确转换:
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
相关产品推荐
相关产品推荐

