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

Spring Boot测试与生产环境JSON序列化不一致问题排查

Spring Boot生产环境与单元测试JSON序列化/反序列化行为不一致问题

问题背景

Spring Boot默认JSON序列化/反序列化在生产环境与单元测试中的表现不一致:通过./gradlew bootRun启动应用时正常工作的功能,在单元测试中却失败。希望找到原因,无需编写自定义配置模拟生产环境,让两者行为一致。

复现环境与代码

使用Spring Initializr创建基于Spring Boot 3.1.2、Java 17的极简API服务,依赖Lombok、Guava、WebFlux(WebFlux对该问题无影响)。

实体类与控制器代码

import lombok.*;
import com.google.common.collect.*;

@Builder
@Value
public class SearchResult {
  @Singular
  ImmutableList<String> results;
}

@RestController
@RequestMapping("/api/v1/search")
final class SearchController {
  @GetMapping(produces = "application/json")
  public Mono<SearchResult> search() {
    return Mono.just(SearchResult.builder().result("result1").result("result2").build());
  }
}

单元测试代码

@AutoConfigureWebTestClient
@SpringBootTest
final class SearchControllerTest {
  @Autowired private WebTestClient webTestClient;

  @Test
  public void search() {
    var response = webTestClient.get()
      .uri("/api/v1/search")
      .header("Content-Type", "application/json")
      .exchange();

    response.expectStatus().isOk();
    response.expectBody(SearchResult.class).isEqualTo(...);
  }
}

测试失败现象

测试失败存在两个核心问题:

  • 使用ImmutableList时出现模糊错误,替换为java.util.List后错误消失;
  • 提示无法反序列化无默认构造方法的对象。

已尝试的临时解决方案

可以通过以下方式临时解决,但不符合需求:

  • 移除@Value注解,改用Java record(需保证不可变性);
  • 在单元测试中自定义ObjectMapper并注册GuavaModule:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.guava.GuavaModule;

@Configuration
public class TestConfig {
  @Bean
  public ObjectMapper objectMapper() {
    var mapper = new ObjectMapper();
    mapper.registerModule(new GuavaModule());
    return mapper;
  }
}

期望的解决方案是让测试直接使用生产代码中已注入的ObjectMapper,保证测试真实性,同时复用生产环境已支持的特性,同时想了解测试与生产环境配置不同的原因。

问题更新

更新1:生产环境反序列化同样存在问题

仅序列化行为符合预期,生产环境中的反序列化行为与单元测试同样失败。通过在SearchController中新增端点验证:

@GetMapping("/test")
public Mono<SearchResult> test() {
  return webClient.get()
      .uri("http://localhost:8080/api/v1/search")
      .exchangeToMono(response ->
          response.statusCode().equals(HttpStatus.OK)
              ? response.bodyToMono(SearchResult.class)
              : response.createError())
      .flatMap(response ->
          Mono.just(response.toBuilder().result("result3").build()));
}

已知GuavaModule可解决Guava相关序列化问题,但希望在不添加显式默认构造方法的前提下,继续使用lombok.Value注解。

更新2:@Value配合Jackson反序列化的解决方案

关于@Value注解配合Jackson反序列化的问题已解决:通过在@Builder注解上添加@Jacksonized,即可让Jackson识别Lombok生成的构建器,无需为SearchResult添加默认构造方法,同时保留@Value带来的不可变性。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 11:25:13