You need to enable JavaScript to run this app. Mockito:@InjectMocks运行正常 手动构造传入mock对象却抛NPE
优惠活动
大模型
产品
解决方案
定价
更多

Mockito:@InjectMocks运行正常 手动构造传入mock对象却抛NPE

问题核心原因

你遇到的NPE和@InjectMocks的特殊能力完全无关,本质是测试类成员变量初始化时机错位 + Java值传递规则共同导致的。

  • 执行顺序错位是根本诱因
    Java类的成员变量初始化顺序是固定的:
    • JUnit实例化测试类时,首先给所有成员变量赋默认值(所有对象类型默认都是null)
    • 立刻执行成员变量声明时的内联初始化代码(也就是你写的UserService userService = new UserService(...)这行)
    • 之后MockitoJUnitRunner才会扫描类上的注解,调用Mockito.initMocks()生成所有@Mock实例,赋值给对应的字段
      也就是说,你手动创建UserService实例的时机,远早于Mockito给mockedTransactionManager、mockedUserDAO这些字段赋值的时机。这时候你传给构造器的都是还没被初始化的null值。
  • 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.31 18:54:00