mockito 应优先模拟接口(如 paymentservice)而非具体类(如 paymentserviceimpl),以避免因构造参数、静态方法或 final 限制导致 mock 失败;需确保依赖通过接口注入,并使用 eq()、any() 等匹配器统一参数风格,复杂对象用 argthat() 或 refeq();spring boot 测试中应使用 @mockbean 替代 @mock 以真正替换容器 bean;对有副作用的操作必须 verify() 验证调用行为,而非仅断言返回值。

Mockito 模拟接口而非具体类更安全
直接 mock 一个具体实现类(比如 PaymentServiceImpl)容易导致测试与内部实现强耦合——一旦类里新增了非空构造参数、或调用了静态方法,mock() 就会失败或抛 NullPointerException。更稳妥的做法是只 mock 接口(如 PaymentService),因为接口无状态、无构造逻辑,且符合依赖倒置原则。
实操建议:
- 确保被测类通过接口注入依赖(如用
@Autowired PaymentService paymentService),而不是直接 new 实现类 - 用
Mockito.mock(PaymentService.class),而非mock(PaymentServiceImpl.class) - 如果只有实现类没有接口,优先补上接口定义;强行 mock 实现类时需留意其构造器和 final 方法是否可被 Mockito 处理(默认不支持 mock final 类/方法)
when().thenReturn() 要匹配真实调用的参数结构
常见错误是写 when(paymentService.charge(100, "USD")).thenReturn(true),但实际业务代码调用的是 paymentService.charge(new BigDecimal("100"), Currency.getInstance("USD")) —— 参数类型、实例对象不一致会导致 stub 不生效,返回 null 或默认值,进而引发 NullPointerException。
实操建议:
- 用
eq()、any()等匹配器时,所有参数都必须用匹配器,不能混用(例如when(x.method(eq(1), "str"))是非法的) - 对复杂对象,优先使用
argThat()自定义断言,或用refEq()做字段级相等判断 - 调试时加
verify(paymentService).charge(...)确认方法确实被调用过,避免 stub 写错却没触发
@MockBean 在 Spring Boot 测试中替代 @Mock 更可靠
纯 @Mock 创建的对象只是普通 mock 实例,不会自动替换 Spring 容器里的 Bean。在 @SpringBootTest 中若仍用 @Mock + 手动 @Autowired 注入,很可能实际运行时还是走的真实 Bean,导致测试不隔离。
实操建议:
- 在集成测试中统一用
@MockBean,它会自动注册为 singleton 并覆盖容器中原有 Bean -
@MockBean和@SpyBean不能混用于同一个类型,否则启动报错;需要部分 mock 时优先选@SpyBean+doReturn().when() - 注意
@MockBean的作用域:类级别生效,若多个测试类共用同一个上下文,可能相互干扰,必要时加@DirtiesContext
verify() 验证行为比断言返回值更能暴露设计问题
只校验结果(如 assertThat(result).isTrue())掩盖了“是否真的调用了外部服务”这个关键点。比如支付失败时本该调用 notificationService.sendFailure(),但测试没验证,就可能漏掉异常路径的逻辑。
实操建议:
- 对所有有副作用的依赖调用(发消息、写 DB、调第三方 API),必须用
verify(dependency).methodName(...)显式确认 - 用
times(1)、never()、atLeastOnce()精确约束调用频次,避免因重试或缓存逻辑导致意外调用 - 不要在 verify 中做复杂断言(如校验对象字段);应先用
ArgumentCaptor捕获参数,再单独 assert
真正难的不是写出 mock,而是判断哪些依赖值得 mock、哪些行为必须验证——这取决于你是否清楚业务边界在哪。外部 HTTP 调用、数据库、消息队列几乎总是要 mock 的,但同模块内的工具类或值对象通常不必。










