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

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)的序列化规则(除非配置了特定的序列化过滤)。

修复方式:
选择以下任意一种方式构造正确的请求体:

  1. 手动编写符合要求的JSON字符串:
    String requestBody = "{\"amount\":2000.0}";
    
  2. 配置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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 11:52:46