Mockito:使用@Spy与@InjectMocks时如何注入构造方法的@Mock修饰final字段
首先,咱们先拆解下你代码里的核心问题:你在声明service时直接手动调用构造方法new ServiceTest(usedAsNonFinal, usedAsFinal),但这时候@Mock修饰的usedAsNonFinal和usedAsFinal还没被Mockito初始化(Mockito的Mock对象是由MockitoJUnitRunner在测试类初始化后期才创建的),所以你传入的其实是null;而且因为usedAsFinal是final字段,一旦通过构造方法赋值后就无法再被修改,后续@InjectMocks也没法替换它。
下面给你两种可行的解决方案:
方案一:让Mockito自动处理构造方法注入(推荐)
去掉手动new的代码,让@Spy和@InjectMocks配合自动完成依赖注入。Mockito的注入优先级是构造方法 > Setter方法 > 字段注入,对于final字段来说,构造方法注入是最贴合Java语法的方式。
修改后的代码如下:
@RunWith(MockitoJUnitRunner.class) public class ServiceTest { @Mock private SomeObject usedAsNonFinal; @Mock private SomeObject usedAsFinal; // 去掉手动new,Mockito会自动匹配构造方法参数注入Mock对象 @Spy @InjectMocks private Service service; @Before public void setUp() { // 这里可以添加Spy的额外初始化逻辑(如果需要) // MockitoAnnotations.initMocks(this); // 可选,因为RunWith已经处理了Mock初始化 } // 你的测试方法... }
Mockito会自动找到Service类的构造方法,根据参数名称(或类型,如果只有一个构造方法的话)匹配@Mock修饰的对象,完成注入。final字段会通过构造方法被正确赋值为Mock对象,完全符合final的特性。
方案二:在@Before方法中手动创建Spy
如果你确实需要手动控制Service的实例化时机,可以把Spy的创建逻辑放到@Before方法里。因为MockitoJUnitRunner会在@Before方法执行前完成所有@Mock对象的初始化,这时候传入的Mock对象已经是有效的实例了。
代码示例:
@RunWith(MockitoJUnitRunner.class) public class ServiceTest { @Mock private SomeObject usedAsNonFinal; @Mock private SomeObject usedAsFinal; private Service service; @Before public void setUp() { // 此时@Mock对象已经被初始化,传入构造方法创建真实对象后再包装为Spy service = Mockito.spy(new Service(usedAsNonFinal, usedAsFinal)); // 如果还有其他非final字段需要注入,可以在这里手动调用Setter方法 } // 你的测试方法... }
这种方式能确保final字段被正确赋值为Mock对象,同时保留Spy的特性——既可以调用真实方法,也可以按需mock部分方法的行为。
额外注意点
- 如果你用的是Mockito 2+版本,确保你的测试类和被测类都符合Mockito的要求(比如不是final类,方法不是private等,不过Spy支持真实对象,所以只要真实对象能被实例化即可)。
- 避免在字段声明阶段直接new被测对象,因为此时Mock对象还未初始化,容易导致空指针或注入失败。
内容的提问来源于stack exchange,提问作者RichardK

