写单元测试时,被测类往往依赖 Repository、远程服务或者消息组件,如果直接把这些真实依赖拉进测试,轻则测试跑得飞快变慢,重则因为环境不通导致用例时红时绿。Mockito 的价值就在这里:它可以在运行时替你“伪造”这些依赖,让测试只关心当前类的逻辑对不对。本文结合 Spring Boot 的实际用法,把 Mockito 的核心机制、常用 API 和容易踩的坑一次讲清楚。

一、Mockito 的核心机制与注解用法
Mockito 的本质是利用动态代理在内存中生成一个实现了目标接口(或继承了目标类)的替身对象。这个对象的所有方法默认都返回 null、0 或者 false,不会产生任何真实的副作用。当你在测试里调用被测类的方法时,被测类内部对依赖的调用实际上落到了这个替身上,从而实现了测试范围的隔离。
在单元测试中使用 Mockito 最常见的方式是配合 @ExtendWith(MockitoExtension.class) 注解,这是 JUnit 5 的写法。配合 @Mock 生成模拟对象,配合 @InjectMocks 把这些模拟对象注入到被测类的构造方法或字段中,一个最小可用的测试骨架如下:
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private OrderRepository orderRepository;
@Mock
private PaymentClient paymentClient;
@InjectMocks
private OrderService orderService;
@Test
void should_create_order_successfully() {
// 打桩:模拟支付接口返回成功
when(paymentClient.pay(anyString())).thenReturn(true);
when(orderRepository.save(any(Order.class)))
.thenAnswer(inv -> inv.getArgument(0));
Order order = orderService.createOrder("SKU-1001", 2);
assertNotNull(order.getId());
// 验证 save 方法被调用了一次
verify(orderRepository, times(1)).save(any(Order.class));
}
}需要注意的是,@InjectMocks 的注入规则是“尽力而为”:它会优先尝试构造器注入,其次是属性注入,最后是 setter 注入。如果被测类有多个构造器或者字段类型重复,注入结果可能不符合预期,此时建议显式在 @BeforeEach 里手动构造被测对象,语义更清晰也更稳定。
二、@Mock 与 @MockBean 的区别与选择
Spring Boot 开发者最容易混淆的就是这两个注解。它们名字相近,但背后的机制完全不同。@Mock 是 Mockito 原生注解,生成的对象与 Spring 容器毫无关系,测试根本不会启动 Spring 上下文,速度快,适合纯单元测试。@MockBean 则是 Spring Boot Test 提供的注解,它会启动 Spring 容器,并把容器中对应类型的 Bean 替换成 Mockito 的模拟对象,适合需要容器参与的切片测试或集成测试。
两者的差异可以用下面这张表概括:
| 对比项 | @Mock | @MockBean |
|---|---|---|
| 来源 | Mockito 原生 | Spring Boot Test |
| 是否启动 Spring 容器 | 否 | 是 |
| 执行速度 | 毫秒级 | 秒级(首次启动) |
| 替换容器 Bean | 不会 | 会 |
| 适用场景 | 纯逻辑单元测试 | 切片测试、集成测试 |
一个常见的误区是在所有测试里无脑使用 @MockBean。由于每次使用 @MockBean 都可能改变上下文配置,Spring 会为新测试类重建 ApplicationContext,导致缓存失效、测试整体变慢。如果只是验证 Service 层的业务逻辑,完全不需要容器,直接用 @Mock 加 @InjectMocks 即可。只有当你需要 @WebMvcTest 或 @SpringBootTest 环境,又想让某个外部依赖不落库、不发真实请求时,才考虑 @MockBean:
@WebMvcTest(OrderController.class)
class OrderControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private OrderService orderService;
@Test
void should_return_order_detail() throws Exception {
given(orderService.findById(1L)).willReturn(new Order(1L, "SKU-1001"));
mockMvc.perform(get("/api/orders/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.sku").value("SKU-1001"));
}
}三、打桩与验证:when、verify 和 ArgumentCaptor
打桩(stubbing)是告诉模拟对象“当某个方法被调用时返回什么”的过程,核心 API 是 when(...).thenReturn(...)。除了固定返回值,thenAnswer 可以根据入参动态计算结果,thenThrow 可以模拟异常分支,thenDoNothing 用于让 void 方法静默通过。覆盖异常分支是很多团队容易遗漏的地方,比如支付接口超时的场景:
@Test
void should_throw_when_payment_timeout() {
when(paymentClient.pay(anyString()))
.thenThrow(new PaymentTimeoutException("connect timeout"));
assertThrows(PaymentTimeoutException.class,
() -> orderService.createOrder("SKU-1001", 2));
// 异常路径下不应该保存订单
verify(orderRepository, never()).save(any());
}验证(verify)用来断言“某个方法是否被调用、被调用了几次、参数是什么”。除了 times 和 never,还有 atLeastOnce、atMost 等模式。当需要检查传给依赖的完整对象内容时,ArgumentCaptor 是最趁手的工具,它可以把方法调用时实际传入的参数捕获下来,再对字段逐个断言:
@Test
void should_save_order_with_correct_fields() {
when(paymentClient.pay(anyString())).thenReturn(true);
orderService.createOrder("SKU-1001", 2);
ArgumentCaptor<Order> captor = ArgumentCaptor.forClass(Order.class);
verify(orderRepository).save(captor.capture());
Order saved = captor.getValue();
assertEquals("SKU-1001", saved.getSku());
assertEquals(2, saved.getQuantity());
assertEquals(OrderStatus.PAID, saved.getStatus());
}四、高频踩坑点与最佳实践
第一个坑是参数匹配器的混用问题。一旦某个参数使用了 any()、eq() 这类匹配器,同一次调用中的其他参数也必须使用匹配器,不能裸写普通值,否则会抛出 InvalidUseOfMatchersException。正确做法是把普通值包一层 eq(),例如 when(client.query(any(), eq(10))).thenReturn(list)。
第二个坑是对 mock 出来的类打桩失效。Mockito 默认不能模拟 final 类和 final 方法(3.4 版本后需引入 mockito-inline 才支持),如果你的依赖是 final 类,打桩会静默不生效,方法仍走真实逻辑。遇到这种情况,要么引入 inline mock maker,要么重新审视设计是否应该面向接口编程。
第三个坑是过度模拟。当测试里把所有依赖都 mock 掉,甚至连简单的值对象也 mock,测试就会变得非常脆弱——被测代码只要稍微重构,测试就大面积报红。一个实用的判断标准是:mock 那些跨越进程边界或涉及 I/O 的依赖(数据库、HTTP、MQ),而值对象、工具类、纯函数直接用真实实现。
最后是可读性建议。测试方法名用 should_xxx_when_xxx 的格式表达意图;每个测试只验证一个行为;必要时使用 BDDMockito 的 given(...).willReturn(...) 风格,让测试读起来更像需求描述。BDD 风格与 when 在功能上完全等价,纯粹是团队口味问题,但统一风格比选择哪种风格更重要。掌握这些原则后,配合 CI 中强制执行的测试覆盖率门禁,Mockito 就能真正成为保障代码质量的基础设施,而不是摆设。
MockitoSpring Boot单元测试Mock Bean修改时间:2026-09-08 21:27:07