Java Controller单元测试JSON响应与实际API不符问题排查
单元测试与REST API返回不符的问题修复
问题概述
测试depositUserBalance端点控制器时遇到两个问题:
- 测试生成的请求体为
{"balance":3000.0},但根据UserAmountDTO的配置,请求体应仅包含amount字段(amount标记为WRITE_ONLY)。 - 测试断言时响应内容为空字符串,报错
org.opentest4j.AssertionFailedError: expected: [{"balance":3000.0}] but was: [],但Postman调用API能正常返回带balance的JSON。
相关代码
控制器代码
@PutMapping("/api/deposit") public ResponseEntity<UserAmountDTO> depositUserBalance(HttpServletRequest request, @RequestBody UserAmountDTO userAmountDTO) { Integer userId = (Integer) request.getAttribute("userId"); userAmountDTO = userService.updateUserBalance(userId, userAmountDTO); return new ResponseEntity<>(userAmountDTO, HttpStatus.OK); }
DTO代码
@Getter @Setter @AllArgsConstructor public class UserAmountDTO { @JsonProperty(access = Access.WRITE_ONLY) public double amount; @JsonProperty(access = Access.READ_ONLY) public double balance; }
原测试代码
@Test public void Test_DepositUserBalance() throws Exception { // Arrange UserAmountDTO userAmountDTO = new UserAmountDTO(2000.0, 3000.0); HttpServletRequest request = mock(HttpServletRequest.class); when(request.getAttribute("userId")).thenReturn(1); when(userService.updateUserBalance(1, userAmountDTO)).thenReturn(userAmountDTO); String requestBody = objectMapper.writeValueAsString(userAmountDTO); System.out.println("requestBody: " + requestBody); // Act MvcResult mvcResult = mockMvc.perform(put(DEPOSIT_PATH) .contentType(MediaType.APPLICATION_JSON) .content(requestBody).with(requestBuilder -> { requestBuilder.setAttribute("userId", 1); return requestBuilder; })) .andExpect(status().isOk()) .andReturn(); // Assert int status = mvcResult.getResponse().getStatus(); String content = mvcResult.getResponse().getContentAsString(); String expectedJson = String.format("{\"balance\":%.1f}", 3000.0); assertEquals(HttpStatus.OK.value(), status); assertEquals(expectedJson, content); }
问题原因及修复
问题1:请求体包含无效的balance字段
原因:直接序列化完整的UserAmountDTO实例时,ObjectMapper默认会序列化所有public字段,忽略了@JsonProperty(access = Access.READ_ONLY)的序列化规则(除非配置了特定的序列化过滤)。
修复方式:
选择以下任意一种方式构造正确的请求体:
- 手动编写符合要求的JSON字符串:
String requestBody = "{\"amount\":2000.0}"; - 配置
ObjectMapper仅序列化WRITE_ONLY字段:ObjectMapper requestMapper = new ObjectMapper(); SimpleFilterProvider filterProvider = new SimpleFilterProvider(); filterProvider.addFilter("writeOnlyFilter", SimpleBeanPropertyFilter.filterOutAllExcept("amount")); requestMapper.setFilterProvider(filterProvider); String requestBody = requestMapper.writeValueAsString(new UserAmountDTO(2000.0, 0));
问题2:响应内容为空
原因:mockuserService.updateUserBalance时使用了具体的userAmountDTO实例作为参数匹配条件,但Spring MVC处理请求时会自动创建新的UserAmountDTO实例,导致mock方法未被触发,返回null,最终控制器返回null,响应内容为空。
修复方式:
使用Mockito参数匹配器替代具体实例,确保任何UserAmountDTO实例调用都能触发mock返回:
// 导入所需类:org.mockito.ArgumentMatchers.eq、org.mockito.ArgumentMatchers.any when(userService.updateUserBalance(eq(1), any(UserAmountDTO.class))).thenReturn(userAmountDTO);
修复后的完整测试代码
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @Test public void Test_DepositUserBalance() throws Exception { // Arrange UserAmountDTO mockResponseDto = new UserAmountDTO(0, 3000.0); when(request.getAttribute("userId")).thenReturn(1); // 用参数匹配器确保mock触发 when(userService.updateUserBalance(eq(1), any(UserAmountDTO.class))).thenReturn(mockResponseDto); // 构造正确的请求体 String requestBody = "{\"amount\":2000.0}"; // Act MvcResult mvcResult = mockMvc.perform(put(DEPOSIT_PATH) .contentType(MediaType.APPLICATION_JSON) .content(requestBody) .with(requestBuilder -> { requestBuilder.setAttribute("userId", 1); return requestBuilder; })) .andExpect(status().isOk()) .andReturn(); // Assert int status = mvcResult.getResponse().getStatus(); String content = mvcResult.getResponse().getContentAsString(); String expectedJson = "{\"balance\":3000.0}"; assertEquals(HttpStatus.OK.value(), status); assertEquals(expectedJson, content); }
内容的提问来源于stack exchange,提问作者Jessica
相关产品推荐
相关产品推荐

