Mockito:@InjectMocks运行正常 手动构造传入mock对象却抛NPE
问题核心原因
你遇到的NPE和@InjectMocks的特殊能力完全无关,本质是测试类成员变量初始化时机错位 + Java值传递规则共同导致的。
- 执行顺序错位是根本诱因
Java类的成员变量初始化顺序是固定的:- JUnit实例化测试类时,首先给所有成员变量赋默认值(所有对象类型默认都是
null) - 立刻执行成员变量声明时的内联初始化代码(也就是你写的
UserService userService = new UserService(...)这行) - 之后MockitoJUnitRunner才会扫描类上的注解,调用
Mockito.initMocks()生成所有@Mock实例,赋值给对应的字段
也就是说,你手动创建UserService实例的时机,远早于Mockito给mockedTransactionManager、mockedUserDAO这些字段赋值的时机。这时候你传给构造器的都是还没被初始化的null值。
- JUnit实例化测试类时,首先给所有成员变量赋默认值(所有对象类型默认都是
- Java值传递规则固化了问题
Java对引用类型的参数传递是传递引用的副本:当你执行
new UserService(mockedTransactionManager)时,只是把当时mockedTransactionManager变量持有的null值的副本传给了构造器,UserService内部会把这个null存到自己的私有字段里。
后续Runner给外部的mockedTransactionManager变量赋值为真实mock对象时,只会改变外部变量的指向,完全不会影响UserService内部已经存好的null引用。
这就导致你看起来mockedTransactionManager是个有效mock,但UserService内部持有的还是构造时传进去的null,调用时自然抛出NPE。 - @InjectMocks无问题的原因
@InjectMocks的处理逻辑是Runner在所有@Mock字段初始化完成之后才执行的:它会先拿到已经赋值好的mock实例,再通过构造器/字段/方法注入的方式创建UserService实例,此时传入的都是已经生效的mock对象,自然不会有NPE。
正确的手动初始化方案
如果不用@InjectMocks,只要把初始化时机放到所有@Mock字段初始化完成之后即可,通常写在@Before注解的方法里:
@Before public void setup() { // 此时所有@Mock字段已经被Runner初始化完成 userService = new UserService(mockedUserDAO, mockedTransactionManager); }
这种写法和@InjectMocks的执行效果完全等价。
另外你给出的手动初始化代码里用到了mockedUserDAO,但测试类的@Mock列表里没有声明这个对象,记得补上对应的@Mock UserDAO mockedUserDAO;,不然会触发编译错误。
内容的提问来源于stack exchange,提问作者Dmytro Moskovchenko
相关产品推荐
相关产品推荐

