Spring 测试基础
约 1267 字大约 4 分钟
布欧-Lewyon
2026-05-15
首页 › Spring Boot › 测试 › Spring 测试基础
Spring Boot 将测试分为两类:切片测试(只加载所需切片)和 集成测试(加载完整上下文)。两者各有适用场景。
测试依赖
spring-boot-starter-test 包含 JUnit 5、Mockito、AssertJ、Hamcrest 等常用测试库:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>@SpringBootTest:集成测试
加载完整的 Spring 上下文,适合端到端验证:
@SpringBootTest
class UserServiceTest {
@Autowired
private UserService userService;
@Test
void shouldCreateUser() {
UserCreateRequest request = new UserCreateRequest();
request.setUsername("test");
request.setAge(20);
User user = userService.create(request);
assertThat(user.getId()).isNotNull();
assertThat(user.getUsername()).isEqualTo("test");
}
}@SpringBootTest 默认会启动一个随机端口的嵌入式 Web 容器(webEnvironment = RANDOM_PORT)。
@WebMvcTest:Controller 切片测试
只加载 Web 层相关 Bean(Controller、ControllerAdvice、Jackson 配置等),不加载 Service 和 Repository,速度更快:
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void shouldReturnUserList() throws Exception {
when(userService.findAll()).thenReturn(List.of(
new User(1L, "张三"),
new User(2L, "李四")
));
mockMvc.perform(get("/api/users")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data[0].username").value("张三"));
}
}MockMvc 常用方法
// GET 请求
mockMvc.perform(get("/api/users/{id}", 1L))
// POST 请求
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"username": "test", "age": 20}
"""))
// 验证结果
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.username").value("test"))
// 打印请求/响应详情(便于调试)
.andDo(print())切片测试类型
Spring Boot 提供了多种切片注解,只加载特定层次的 Bean:
| 注解 | 加载范围 |
|---|---|
@WebMvcTest | Web 层(Controller) |
@DataJpaTest | JPA Repository(内嵌数据库) |
@JdbcTest | JDBC 相关 |
@JsonTest | JSON 序列化/反序列化 |
@RestClientTest | REST 客户端 |
@DataJpaTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void shouldFindByUsername() {
User user = new User("张三");
userRepository.save(user);
Optional<User> found = userRepository.findByUsername("张三");
assertThat(found).isPresent();
}
}源码与架构:测试上下文缓存机制
每次运行 @SpringBootTest 都会创建完整的 Spring 上下文。如果每个测试类都创建一次上下文,测试套件总体耗时将线性增长。Spring Test 通过 ContextCache 复用已创建的上下文:
TestContextManager
│
└── prepareTestInstance(testInstance)
└── ① 根据测试类构建 MergedContextConfiguration
├── locations / classes(配置位置)
├── activeProfiles(@ActiveProfiles)
├── contextInitializers
├── parent(@ContextHierarchy)
└── ...
│
├── ② 在 ContextCache 中查找 key = mergedConfig → 命中?使用缓存的 context
│
└── ③ 未命中 → 创建新的 ApplicationContext
├── 创建 + 刷新上下文
├── 缓存到 ContextCache(最大容量默认 32,可通过 spring.test.context.cache.maxSize 调整)
└── 返回 contextSpring 默认使用 DefaultContextCache,最大容量 32 个上下文。超过时按最近最少使用(LRU)淘汰。当测试类使用不同的 @ActiveProfiles、不同的 @SpringBootTest(classes=...) 或不同的 @MockBean 时,都会产生不同的 MergedContextConfiguration key,从而创建新的上下文。
@DirtiesContext 对缓存的冲击
@DirtiesContext 强制 Spring 在测试方法前后重建上下文——这会导致缓存中对应的上下文失效并被移除。每使用一次 @DirtiesContext 就意味着一个上下文从缓存中被废弃,后续同配置的测试需要重新创建。
// 场景:10 个测试类,每类 5 个 @Test 方法
// 使用 @DirtiesContext(methodMode = AFTER_METHOD) → 创建 50 次上下文!
// 不使用 → 最多创建 10 次(每类 1 次)生产意识:
@MockBean是缓存命中的最大变数——每次在测试类中使用@MockBean,Spring 都会在MergedContextConfiguration中记录 bean 定义覆盖,生成新的 key。如果两个测试类 mock 了不同的 Bean 集合,即使其他配置完全相同,也会产生两个不同的上下文。多个测试类中 mock 了相同的 Bean 时,上下文可以被复用。建议:①将相似 mock 场景的测试放在同一个测试类中;②通过@TestConstructor("autowireMode = AutowireMode.ALL")减少 mock Bean 数量;③将 @MockBean 数量控制在 3-5 个以内,过多会降低复用率。@ContextConfiguration(initializers = ...)比@MockBean更轻量——如果只是替换某些配置值而非 mock 整个 Bean,优先使用ApplicationContextInitializer。
测试配置隔离
测试可以拥有独立的 application-test.yml,并激活 test Profile:
# src/test/resources/application-test.yml
spring:
datasource:
url: jdbc:h2:mem:testdb
jpa:
hibernate:
ddl-auto: create-drop@SpringBootTest
@ActiveProfiles("test")
class SomeIntegrationTest {
// 使用测试配置
}生产意识:
@SpringBootTest默认自动发现配置(@SpringBootConfiguration),但如果测试类不在应用入口包内或子包下,需要显式指定@SpringBootTest(classes = DemoApplication.class)。@MockBean会在不同测试类间共享上下文——如果 A 测试 mock 了 Service,B 测试想用真实 Service,需要使用@DirtiesContext让 Spring 重建上下文,但会显著拖慢测试套件。建议对同一 Controller 的多个测试用例写在同一个测试类中,避免上下文重建。
小结
@SpringBootTest:加载完整上下文,适合集成测试。@WebMvcTest:只加载 Web 层,配合MockMvc测试 Controller,速度快。@MockBean代替真实依赖,隔离测试范围。- 切片测试按需选型:
@DataJpaTest测 Repository、@JsonTest测序列化。 - 易错:
@WebMvcTest不加载@Service/@Repository,所以需要@MockBeanMock Service。@MockBean会在上下文重置时失效,多次测试间如果改变 Mock 行为,使用@DirtiesContext或每个测试方法独立设置 mock。 - 思考任务:为已有的 UserController 写
@WebMvcTest,Mock UserService,用 MockMvc 验证 GET /api/users 的响应状态码和 JSON 结构。
上一节:参数校验与异常处理
下一节:API 文档
