@SpringBootTest、@WebMvcTest 和 @DataJpaTest 有什么区别?
简化版
@SpringBootTest 加载接近完整的 Boot ApplicationContext,适合集成测试;@WebMvcTest 只加载 MVC 相关切片,@DataJpaTest 只加载 JPA 实体、Repository 和相关基础设施。测试范围越大越接近真实运行,但启动更慢、失败定位更宽,应按验证目标选择最小足够上下文。
详细版
SpringBootTest 默认 webEnvironment=MOCK,有 Web 能力时使用模拟环境但不启动真实服务器;RANDOM_PORT 或 DEFINED_PORT 才启动监听端口。WebMvcTest 常配 MockMvc 验证路由、绑定、校验、序列化和 MVC 安全链,并用测试替身补齐 Controller 依赖。
DataJpaTest 默认具有事务测试语义,通常在每个测试后回滚,并在可用时配置嵌入式数据库;若要验证真实数据库方言、索引或迁移,应显式使用测试容器或真实测试数据库,不能只凭内存数据库通过就下结论。
完整版教学
一、先选择测试边界
不需要 Spring 的业务算法应写普通单元测试,直接 new 对象并注入替身,速度最快。只有要验证 Bean 装配、配置绑定、数据库映射、MVC 转换或完整启动行为时,才加载相应 Spring 上下文。
Boot 会缓存配置相同的测试上下文,减少重复启动成本;频繁改变属性、Mock 定义或使用 @DirtiesContext 会降低复用率。
二、SpringBootTest 的四种 Web 环境
MOCK:默认值,创建 Web ApplicationContext 和模拟 Servlet 环境,不监听真实端口。RANDOM_PORT:启动真实服务器并使用随机端口。DEFINED_PORT:按配置端口启动真实服务器。NONE:创建非 Web ApplicationContext。
RANDOM_PORT 适合端到端 HTTP 测试。此时测试方法和服务器处理请求位于不同线程,测试方法上的 @Transactional 不能把服务器线程中的数据库修改自动纳入同一个事务并在测试结束回滚。
三、WebMvcTest 验证什么
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired MockMvc mvc;
}
它聚焦 Controller、MVC 配置、参数与返回值处理、JSON 转换、ControllerAdvice、Filter 和相关安全自动配置,不会像完整应用那样扫描所有 Service 与 Repository。Controller 的业务依赖需要通过当前版本支持的测试替身机制显式提供。
WebMvcTest 不是纯单元测试,也不是完整应用测试。若自定义 MVC 配置、SecurityFilterChain 或异常处理不在切片选择范围内,需要显式导入,否则测试环境可能与生产不同。
四、DataJpaTest 验证什么
DataJpaTest 聚焦实体映射、Spring Data Repository、EntityManager 和 JPA 基础设施。默认回滚有利于测试隔离,但也可能让 flush 或 commit 阶段错误没有自然暴露,因此关键写入后应主动 flush,或设计专门的提交边界测试。
内存数据库与生产数据库在 SQL 方言、大小写、锁、JSON 类型和索引行为上可能不同。Testcontainers 配合 Boot 的连接服务支持,可以用接近生产的数据库运行集成测试,同时保持环境可重复。
五、其他测试切片
Boot 还提供 @JsonTest、@WebFluxTest、@RestClientTest 等切片,分别聚焦 JSON、响应式 Web、HTTP 客户端。切片注解带有各自的 TypeExcludeFilter 和自动配置集合,不应随意叠加多个 @...Test 期望拼成完整上下文。
如果验证目标跨越 Web、Service 和数据库多个层次,直接使用 SpringBootTest 往往比强行扩张切片更清楚。切片的意义是明确边界,不是追求注解越多越好。
六、如何形成测试金字塔
大量快速单元测试覆盖分支逻辑;适量切片测试验证框架边界;少量 SpringBootTest 或真实 HTTP 测试验证关键链路。外部数据库、消息队列可用 Testcontainers 创建可重复依赖,但仍要控制测试数量和启动成本。
测试通过的标准也要与环境一致:使用相同序列化配置、时区、数据库版本和安全规则,才能降低“测试绿、生产错”的概率。
七、用成本与覆盖矩阵选测试
假设完整上下文冷启动 8 秒,WebMvcTest 需 1.5 秒,普通单元测试只需 30 毫秒。若 200 个测试都创建互不复用的完整上下文,光启动理论上就需 1600 秒;若大部分变成单元与切片测试,并让相同配置共享缓存,反馈时间会显著下降。数字不是固定基准,重点是测试边界直接决定启动和故障定位成本。
| 验证目标 | 推荐方式 | 是否完整上下文 | 是否真实端口 |
|---|---|---|---|
| 纯业务分支 | JUnit + 替身 | 否 | 否 |
| 路由、绑定、校验、JSON | WebMvcTest | MVC 切片 | 否 |
| 实体映射与 Repository | DataJpaTest | JPA 切片 | 否 |
| Bean 装配与跨层协作 | SpringBootTest(MOCK) | 是 | 否 |
| 真实 HTTP 链路 | SpringBootTest(RANDOM_PORT) | 是 | 是 |
| 生产数据库特性 | DataJpaTest 或完整测试 + Testcontainers | 按目标决定 | 通常否 |
fast / narrow slow / broad
unit tests → MVC/JPA slices → full context → real HTTP + real dependencies
数量多 数量适中 关键链路少量
切片失败不一定说明生产失败,切片成功也不证明完整装配正确。它只验证被纳入边界的组件,所以自定义 Jackson Module、SecurityFilterChain 或 WebMvcConfigurer 如果没被选择或显式导入,测试与生产可能产生差异。
选择钩子:先写下“本测试必须证明什么”,再加载最小足够上下文;不要先选 @SpringBootTest 再想测试目标。
八、常见误区与追问
- 误区:@SpringBootTest 默认会启动真实服务器。 默认 MOCK 不监听端口,只有 RANDOM_PORT 或 DEFINED_PORT 等真实 Web 环境才启动服务器。
- 误区:@WebMvcTest 会自动扫描全部 Service 和 Repository。 它限制到 MVC 切片,Controller 的业务依赖通常要显式提供测试替身。
- 误区:DataJpaTest 使用内存数据库通过就等于生产数据库正确。 方言、锁、JSON、索引和大小写行为可能不同,关键特性应使用相同数据库版本验证。
- 追问:RANDOM_PORT 测试为何不能靠测试线程事务自动回滚服务器写入? 客户端测试与服务器请求位于不同线程和事务,测试方法的事务不包住服务器事务。
- 追问:DataJpaTest 为什么要主动 flush? 某些 SQL、约束与转换错误到 flush 或 commit 才暴露,默认回滚可能掩盖它们。
- 追问:@DirtiesContext 有什么成本? 它使缓存上下文失效,后续测试需要重新创建;应只在确实污染不可恢复状态时使用。
九、加强记忆
SpringBootTest 是完整上下文,WebMvcTest 切 MVC,DataJpaTest 切 JPA;能用单元测试就不启容器,需要框架边界才选切片,跨层链路再用完整集成测试。还要记住 RANDOM_PORT 的服务器事务不在测试线程回滚范围内。