
本文详解如何通过构造函数注入 mock 依赖,解决因未将 mock 实例注入被测对象而导致的 Wanted but not invoked 验证失败问题。
本文详解如何通过构造函数注入 mock 依赖,解决因未将 mock 实例注入被测对象而导致的 `wanted but not invoked` 验证失败问题。
在使用 Mockito 进行单元测试时,一个常见误区是:仅声明 @Mock 注解的依赖,却未将其实际注入到被测对象(SUT)中。例如,ClassA 通过 @Autowired 声明了 Bean1 bean1,但在测试中若仅创建 new ClassA(),其内部 bean1 字段仍为 null(或 Spring 容器未介入时的默认值),导致 handle() 方法调用的是空引用或真实实例,而非你期望的 mock 对象——这正是报错 Wanted but not invoked: bean1.method1(...) 的根本原因。
要使 mock 生效,必须确保被测对象使用的是你控制的 mock 实例。推荐做法是为类添加显式构造函数,支持依赖注入(即“可测试性设计”):
public class ClassA {
private final Bean1 bean1; // 建议改为 final + 构造注入,提升不可变性与可测性
// 主构造函数(供 Spring 自动装配)
@Autowired
public ClassA(Bean1 bean1) {
this.bean1 = bean1;
}
// 保留无参构造函数(如需兼容旧代码,但非必需)
public ClassA() {
this(null);
}
public ClassC handle(ClassD d) {
ClassC c = bean1.method1(d);
return methodA(c);
}
// 示例辅助方法(需在测试中一并 mock 或 stub,此处略)
private ClassC methodA(ClassC input) {
// 实际逻辑...
return input;
}
}
对应测试代码应主动传入 mock 实例,并利用 MockitoExtension 管理 mock 生命周期:
@ExtendWith(MockitoExtension.class)
class ClassATest {
@Mock
ClassD d;
@Mock
Bean1 bean1;
@Mock
ClassC c;
private ClassA sut;
@BeforeEach
void setUp() {
sut = new ClassA(bean1); // ✅ 关键:将 mock 实例注入被测对象
}
@Test
void handleTest() {
// Arrange:配置 mock 行为
when(bean1.method1(d)).thenReturn(c); // 推荐用 when() 而非 lenient().when(),除非有特殊需要
// Act
ClassC result = sut.handle(d);
// Assert:验证交互与结果
verify(bean1).method1(d); // 更精确:验证是否用指定参数调用
assertThat(result).isSameAs(c);
}
}
⚠️ 注意事项:
- 避免 @Autowired + new ClassA() 混用:Spring 的 @Autowired 仅在容器管理的 Bean 中生效;手动 new 实例时该字段不会自动注入。
- 优先使用构造注入而非字段注入:既符合 Spring 最佳实践,也天然支持单元测试,增强类的内聚性与可维护性。
- 慎用 lenient():它会忽略未声明的 mock 调用,掩盖潜在逻辑错误;仅在确实需要忽略某些副作用调用时使用。
- 验证粒度建议:verify(bean1).method1(d) 比 verify(bean1).method1(any()) 更精准,能捕获参数误传问题。
总结:Mockito 的核心原则是「控制依赖」——只有当被测对象真正持有你创建的 mock 实例时,行为模拟与调用验证才有效。通过重构为构造注入,不仅能解决当前问题,更能显著提升代码的可测试性与设计质量。











