Spring Boot 单元测试切片

FreeGuideOnline 最新 2026-07-12

Spring Boot 单元测试切片完全指南

测试切片(Test Slices)是 Spring Boot 提供的一套用于只加载应用程序局部上下文的测试注解机制。通过使用切片,你不再需要启动整个 @SpringBootTest 的完整应用,从而大幅提升测试执行速度、降低环境复杂度,并让测试真正聚焦于被测试的单一层次。

为什么需要测试切片

在典型的 Spring Boot 测试中,仅使用 @SpringBootTest 会加载所有 Bean、数据库连接、消息中间件、Web 服务器等。这会导致:

  • 测试启动慢,通常需要数十秒甚至更久
  • 无关 Bean 的初始化错误可能阻塞测试
  • 测试范围不明确,难以定位问题
  • 难以模拟外部依赖

测试切片只加载与被测组件相关的 Spring 基础设施,其余层次用 Mock 替代,让你可以快速、专注、可靠地测试每一层逻辑。

核心切片注解一览

Spring Boot 提供了一组开箱即用的切片注解,每个注解针对特定层次:

  • @WebMvcTest – 只加载 Spring MVC 相关组件(Controller、ControllerAdvice、Filter 等)
  • @WebFluxTest – 只加载 WebFlux 相关组件
  • @DataJpaTest – 只加载 JPA 相关组件(EntityManager、DataSource、Repository 等)
  • @DataJdbcTest – 只加载 JDBC 相关组件(用于 Spring Data JDBC)
  • @JdbcTest – 极简的 JDBC 切片,仅 DataSource 和 JdbcTemplate
  • @JsonTest – 只加载 JSON 序列化/反序列化相关的 Jackson 组件
  • @RestClientTest – 只加载 RestTemplate 和 RestClient 支持组件
  • @DataMongoTest – 只加载 MongoDB 相关组件(需要 Spring Data MongoDB)
  • @DataRedisTest – 只加载 Redis 相关组件(需要 Spring Data Redis)
  • @DataNeo4jTest – 只加载 Neo4j 相关组件

每个切片都会自动禁用不必要的自动配置,并帮你自动注入相关工具 Bean(例如 @WebMvcTest 会自动注入 MockMvc@DataJpaTest 会自动注入 TestEntityManager)。

实战:Web 层测试切片 – @WebMvcTest

当你只想测试 Controller 的请求映射、参数校验、返回状态码、异常处理时,使用 @WebMvcTest 是标准做法。它不会加载 Service 或 Repository Bean,你需要用 @MockBean 来模拟下层依赖。

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void shouldReturnUserWhenExists() throws Exception {
        User mockUser = new User(1L, "zhangsan");
        when(userService.getUserById(1L)).thenReturn(mockUser);

        mockMvc.perform(get("/users/1"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.name").value("zhangsan"));
    }
}

在上面的代码中,只有 UserController 及其需要的 MVC 基础设施被加载。UserService 被 Mock,测试重点完全放在 HTTP 接口行为上。

@WebMvcTest 的高级用法:

  • 可以额外包含 @Import 引入自定义配置或组件
  • 使用 @Autowired 注入 WebApplicationContext 进行更灵活的测试
  • 配合 @Validated 测试 Bean Validation 场景

实战:持久层测试切片 – @DataJpaTest

当你需要测试 Repository 接口的自定义方法、JPA 映射、JPQL/HQL 查询时,@DataJpaTest 会配置内存数据库(如 H2)并通过自动扫描实体类来创建测试表。测试默认会在事务中运行,并在结束后回滚,保证测试隔离。

@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private UserRepository userRepository;

    @Test
    void shouldFindByName() {
        User user = new User("lisi");
        entityManager.persistAndFlush(user);

        List<User> users = userRepository.findByName("lisi");
        assertThat(users).hasSize(1);
        assertThat(users.get(0).getName()).isEqualTo("lisi");
    }
}

TestEntityManagerEntityManager 的便捷封装,可以直接持久化实体并立即刷新。如果你需要关闭默认的回滚行为,可以在类或方法上添加 @Rollback(false)

定制数据源: @DataJpaTest 默认使用嵌入式数据库。如果想用真实 MySQL 或 PostgreSQL 进行测试,可用 @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) 并配合 Testcontainers 或实际数据源配置。

实战:JSON 层测试切片 – @JsonTest

当你编写的 DTO、序列化配置、@JsonFormat@JsonProperty 等需要验证 JSON 输出时,直接使用 @JsonTest 可以加载 Jackson 的 ObjectMapper 以及你的自定义 Module。

@JsonTest
class UserDtoJsonTest {

    @Autowired
    private JacksonTester<UserDto> json;

    @Test
    void shouldSerializeCorrectly() throws IOException {
        UserDto dto = new UserDto(1L, "wangwu", "wangwu@example.com");
        assertThat(json.write(dto))
                .hasJsonPathNumberValue("$.id")
                .extractingJsonPathStringValue("$.email")
                .isEqualTo("wangwu@example.com");
    }
}

JacksonTester 使用断言风格检查 JSON 结构与值。如需执行反序列化测试,可调用 json.parse(jsonString) 进行验证。

实战:REST 客户端测试切片 – @RestClientTest

当应用中用 RestTemplateRestClient 调用外部 REST 服务时,你想测试自己的 HTTP 客户端配置、错误处理、响应转换,而不实际发起网络请求。@RestClientTest 会自动配置 MockRestServiceServer

@RestClientTest(ExternalApiClient.class)
class ExternalApiClientTest {

    @Autowired
    private MockRestServiceServer server;

    @Autowired
    private ExternalApiClient client;

    @Test
    void shouldReturnParsedResponse() {
        this.server.expect(requestTo("/external/users/1"))
                   .andRespond(withSuccess("{\"id\":1,\"name\":\"zhaoliu\"}", MediaType.APPLICATION_JSON));

        UserDto user = client.getUserById(1L);
        assertThat(user.getName()).isEqualTo("zhaoliu");
    }
}

你可以使用 MockRestServiceServer 来模拟服务端响应,验证请求路径、请求头、请求体等细节,完全无网络依赖。

自定义测试切片与组合注解

当上述内置切片不满足需求时,你可以自行组合注解来定义自己的切片。例如,你想同时测试 Service 层和 Repository 层,但排除 Controller 和 Web 环境:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@SpringBootTest
@AutoConfigureTestDatabase
@AutoConfigureDataJpa
public @interface ServiceSliceTest {
}

然后在测试类上使用:

@RunWith(SpringRunner.class)
@ServiceSliceTest
class ProductServiceTest {
    @Autowired
    private ProductService productService;

    @Autowired
    private ProductRepository productRepository;

    // 业务逻辑与数据库交互的测试
}

通过组合已有切片自动配置类,可以构建出适合团队架构的精准测试切片。

切片测试最佳实践

  1. 一个切片只测一层
    Controller 测试不该依赖真实 Service,Repository 测试也不该启动 Spring MVC。用 @MockBean 替代跨层依赖。

  2. 首选内存数据库,慎用真实数据库
    对于 @DataJpaTest,H2 或 HSQLDB 足够大多数场景。集成测试阶段再用真实数据库或 Testcontainers。

  3. 利用事务回滚保证数据清洁
    切片默认开启事务并在测试后回滚,避免数据互相污染。

  4. @MockBean 精确模拟行为
    Mock 的粒度最好是单个依赖,不要过度使用 @MockBean 注入一堆无需 Mock 的 Bean。

  5. 结合 AssertJ、Hamcrest 增强可读性
    切片测试通常返回 JSON、实体等,利用流式断言工具能使测试意图更明确。

  6. 控制 @WebMvcTest 的 Controller 列表
    如果只测一个 Controller,在 @WebMvcTest(UserController.class) 中指明,避免扫描全部 Controller 类。

  7. 仅在必要时关闭回滚
    例如需要测试事务传播或持久化行为时,才使用 @Rollback(false),并手动清理数据。

性能对比:切片 vs 全量启动

以一个常见的中型 Spring Boot 应用为例:

测试方式 平均启动时间 内存占用
@SpringBootTest 8~15秒 500+ MB
@WebMvcTest 1~2秒 200 MB
@DataJpaTest 1~3秒 250 MB
@JsonTest < 500毫秒 100 MB

切片让单元测试真正做到“秒级反馈”,在 TDD 和 CI 流水线中优势明显。

常见问题排查

  • No qualifying bean of type '...' available
    说明切片未加载你依赖的 Bean。检查是否使用了正确的切片注解,或将缺失的 Bean 用 @MockBean@Import 引入。

  • @WebMvcTestMockMvc 为 null
    确保测试类被 @AutoConfigureMockMvc 含纳(在 @WebMvcTest 中已自动包含,若未生效可手动添加)。

  • @DataJpaTest 找不到实体
    检查实体类是否在 @EntityScan 扫描路径下,或是否在 Spring Boot 主类同包或子包内。

  • 内存数据库报语法错误
    H2 默认模式与 MySQL 差异较大,可在 src/test/resources/application.properties 中添加 spring.jpa.database-platform=org.hibernate.dialect.H2Dialect 明确方言,或使用与生产一致的 Testcontainers 数据库。

总结

Spring Boot 测试切片通过“最小化加载上下文”的思想,让你可以按层次对应用进行快速、隔离的单元测试。关注不同切片适用的范围,合理使用 @MockBean 和自动注入的测试工具,你将能构建出高效且可维护的测试体系。一旦掌握了切片测试,你会发现原本笨重的集成测试可以被拆解为一组轻盈的专项测试,极大提升开发体验和项目质量。