
本文详解 Spring Boot 集成测试中因错误使用 @MockBean 导致服务方法未实际执行的问题,提供控制器测试与服务层测试两种场景下的规范写法、关键配置要点及可运行示例。
本文详解 spring boot 集成测试中因错误使用 `@mockbean` 导致服务方法未实际执行的问题,提供控制器测试与服务层测试两种场景下的规范写法、关键配置要点及可运行示例。
在 Spring Boot 的分层测试实践中,一个高频陷阱是:控制器测试看似通过,但服务逻辑(如 categoryService.findById())根本未被执行,返回值为 null 或空响应。根本原因在于测试配置中对服务类的不当“隔离”——本应参与真实调用的 CategoryService 被声明为 @MockBean,导致其所有方法默认返回 null(除非显式 when(...).thenReturn(...)),从而跳过了真实的业务逻辑和 MapStruct 映射流程。
✅ 正确策略:明确测试目标,选择对应注入方式
| 测试目标 | CategoryService 应如何注入? | 关键说明 |
|---|---|---|
| 测试 Controller(端到端行为) | @Autowired(真实 Bean) + @MockBean CategoryRepository | 确保服务逻辑走通,仅仓库层模拟数据 |
| 测试 Service(业务逻辑+映射) | @Autowired(真实 Bean) + @MockBean CategoryRepository + @SpyBean CategoryResponseMapper | 服务真实运行,仓库可控,Mapper 可验证调用 |
| 纯 Controller 单元测试(不启动容器) | @MockBean CategoryService + 显式 when(categoryService.findById(...)).thenReturn(...) | 完全隔离服务,仅验证 Controller 请求/响应编排 |
⚠️ 注意:@Spy(非 @SpyBean)在 @SpringBootTest 中无法被 Spring 容器管理,其方法不会被代理增强,categoryResponseMapper::toDto 实际调用的是原始 Spy 对象(未初始化或未注入依赖),极易引发 NPE。务必改用 @SpyBean。
? 示例:修复后的 Controller 集成测试(推荐)
@SpringBootTest(classes = {
CategoryController.class,
CategoryService.class,
CategoryResponseMapperImpl.class // 确保 Mapper 实现类被加载
})
class CategoryControllerTest {
@Autowired
private CategoryController categoryController;
@Autowired // ✅ 真实服务实例,参与调用链
private CategoryService categoryService;
@MockBean // ✅ 仅仓库层模拟
private CategoryRepository categoryRepository;
@SpyBean // ✅ 使用 @SpyBean 替代 @Spy,确保 Spring 管理和依赖注入
private CategoryResponseMapper categoryResponseMapper;
@Test
void test_findById_controller_integration() throws Exception {
// 准备测试数据
LocalDateTime dateTime = LocalDate.of(2022, 1, 1).atStartOfDay();
Category category = new Category(123L, "Category", 1);
// 模拟仓库返回
when(clock.instant()).thenReturn(dateTime.atZone(ZoneId.of("UTC")).toInstant());
when(categoryRepository.findById(123L)).thenReturn(Optional.of(category));
// 执行请求
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(categoryController).build();
mockMvc.perform(get("/api/v1/categories/{id}", 123L))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(content().string(
"{\"timestamp\":1640995200000,\"message\":\"Success\",\"data\":{\"id\":123,\"name\":\"Category\",\"ordinal\":1}}"
));
// 验证 Mapper 是否被调用(可选断言)
verify(categoryResponseMapper).toDto(eq(category));
}
}
? 关键注意事项总结
- 避免 @MockBean 误用于核心服务:@MockBean 会完全替换 Spring 容器中的 Bean,若服务含复杂逻辑(如事务、AOP、依赖注入),必须用 @Autowired 加载真实实例。
- @SpyBean 是 @Spy 的安全替代:它由 Spring 管理,支持依赖注入和 AOP 增强,确保 toDto() 等方法能正常执行。
- 显式指定 classes 参数需谨慎:仅当需要最小化上下文时使用;通常建议用 @Import({CategoryService.class, CategoryResponseMapperImpl.class}) 或直接 @SpringBootTest 让 Spring 自动扫描。
- 验证而非假设:添加 verify(...) 断言关键 Mapper 或 Repository 方法是否被调用,避免“测试通过但逻辑未执行”的假象。
遵循以上原则,即可精准控制测试边界,在保证可测性的同时,真实验证业务逻辑、数据转换与异常处理的完整性。










