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

@WebMvcTest单属性请求始终返回400错误,求排查原因

问题:单个字段请求POJO导致Spring MVC测试返回400 Bad Request,添加无用字段后恢复正常

问题场景

控制器端点代码:

@PostMapping("/{productId}/activation/")
public ResponseEntity<ProductResponse> toggleProductActivation(@PathVariable("productId") UUID productId,
                                                               @RequestBody @Valid ToggleProductActivationRequest toggleProductActivationRequest)
        throws ApplicationException {
    var product = toggleProductActivationUseCase.handle(productCommandAssembler.assembleToggleProductActivationCommand(
            toggleProductActivationRequest, productId));
    return ResponseEntity.ok()
                         .body(productResponseAssembler.assembleProductResponse(product));
}

原请求POJO(仅单个字段):

@Builder
@Getter
public class ToggleProductActivationRequest {

    @NotNull
    private Boolean active;
}

单元测试代码:

@WebMvcTest(value = ProductAdminController.class)
class ProductAdminControllerTest {

    private static final String EXCEPTION_MESSAGE = "Exception";

    @Autowired
    protected ObjectMapper objectMapper;
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private ToggleProductActivationUseCase toggleProductActivationUseCase;
    @MockBean
    private ProductCommandAssembler productCommandAssembler;
    @MockBean
    private ProductResponseAssembler productResponseAssembler;

    @Test
    void whenToggleProductActivation_givenProductDoesNotExist_thenReturnNotFound404() throws Exception {
        // given
        var productId = UUID.randomUUID();
        var toggleProductActivationRequest = givenToggleProductActivationRequest();
        var toggleProductActivationCommand = mock(ToggleProductActivationCommand.class);
        when(productCommandAssembler.assembleToggleProductActivationCommand(any(ToggleProductActivationRequest.class),
                                                                            eq(productId),
                                                                            eq(null))).thenReturn(toggleProductActivationCommand);
        when(toggleProductActivationUseCase.handle(toggleProductActivationCommand)).thenThrow(new NotFoundException(
                EXCEPTION_MESSAGE));

        // when & then
        mockMvc.perform(post("/api/v1.0/admin/products/" + productId.toString() + "/activation/")
                                .contentType(MediaType.APPLICATION_JSON)
                                .content(objectMapper.writeValueAsString(toggleProductActivationRequest)))
               .andExpect(status().isNotFound())
               .andExpect(jsonPath("$.httpStatus").value(404))
               .andExpect(jsonPath("$.reason").value(EXCEPTION_MESSAGE));
    }

    private ToggleProductActivationRequest givenToggleProductActivationRequest() {
        return ToggleProductActivationRequest.builder()
                                             .active(true)
                                             .build();
    }
}

现象:运行测试时始终返回400 Bad Request,但给POJO添加一个无用的test字段后,测试能正常返回预期的404结果。

原因分析

这是Lombok @Builder与Jackson序列化/反序列化的兼容问题,核心点:

  • 当POJO仅含单个字段时,Lombok生成的Builder不会附带无参构造函数,Jackson默认尝试通过构造函数反序列化请求体,但布尔类型字段的参数映射容易出现匹配失败,导致请求体反序列化失败。
  • @Valid注解触发的参数校验会在业务逻辑执行前就因为反序列化失败返回400,无法进入后续代码抛出NotFoundException。
  • 添加第二个字段后,Lombok生成的Builder结构发生变化,Jackson可以通过字段映射或Builder方法完成反序列化,绕过了构造函数匹配的问题。

解决方案

无需添加无用字段,以下几种方式可修复:

1. 为POJO添加无参构造函数

配合Lombok的@NoArgsConstructor和@AllArgsConstructor(@Builder需要全参构造函数支持):

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ToggleProductActivationRequest {
    @NotNull
    private Boolean active;
}

2. 指定Jackson使用Builder反序列化

通过@JsonDeserialize明确告诉Jackson使用Lombok生成的Builder类处理反序列化:

@Builder
@Getter
@JsonDeserialize(builder = ToggleProductActivationRequest.ToggleProductActivationRequestBuilder.class)
public class ToggleProductActivationRequest {
    @NotNull
    private Boolean active;
}

3. 改用@Data注解(若业务允许)

@Data会自动生成无参构造函数和setter方法,Jackson可正常处理单个字段的反序列化:

@Data
@Builder
public class ToggleProductActivationRequest {
    @NotNull
    private Boolean active;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 18:24:28