
当被测类构造函数依赖于 Mock 对象的方法返回值时,@InjectMocks 会先完成注入再执行 @BeforeEach,导致存根未生效而初始化失败;此时应放弃自动注入,改用手动构造实例并显式传入已存根的 Mock 对象。
当被测类构造函数依赖于 mock 对象的方法返回值时,`@injectmocks` 会先完成注入再执行 `@beforeeach`,导致存根未生效而初始化失败;此时应放弃自动注入,改用手动构造实例并显式传入已存根的 mock 对象。
在使用 Mockito 进行单元测试时,@Mock 和 @InjectMocks 是常用组合,但它们存在一个关键的执行顺序限制:@InjectMocks 的字段注入发生在测试方法执行前(即早于 @BeforeEach)。这意味着,若被测类(如 A)在构造函数中立即调用被注入依赖(如 B)的方法(例如 b.getC()),而该方法尚未被 when(...).thenReturn(...) 存根,则默认返回 null,进而触发 requireNonNull 抛出 NullPointerException,导致测试甚至无法启动。
上述问题的根本原因在于 @InjectMocks 并非“延迟注入”,而是由 MockitoExtension 在测试生命周期早期(beforeEach 之前)完成字段赋值和构造器调用。因此,在 @BeforeEach 中对 @Mock B b 进行的 stubbing 已然“为时已晚”。
✅ 正确解法:绕过 @InjectMocks,手动控制对象创建时机
@ExtendWith(MockitoExtension.class)
class ATest {
@Mock
B b;
A a; // 不加 @InjectMocks
@BeforeEach
void setUp() {
when(b.getC()).thenReturn("mocked-value"); // ✅ 存根先于构造执行
a = new A(b); // ✅ 手动构造,确保依赖已就绪
}
@Test
void test() {
assertNotNull(a.c);
assertEquals("mocked-value", a.c);
}
}
? 优势说明:
- 时序可控:stubbing 与构造严格按需顺序执行;
- 语义清晰:测试意图一目了然——“我用这个 Mock 创建 A”;
-
调试友好:避免
@InjectMocks的黑盒行为(如字段注入 vs 构造器注入的歧义); - 兼容性强:适用于 final 字段、不可变对象、复杂初始化逻辑等场景。
⚠️ 注意事项:
- 若类有多个构造器,请确保调用的是目标构造器(通常含依赖参数的那个);
- 避免在
setUp()外部(如字段初始化块)创建被测实例,否则仍可能触发早于 stubbing 的构造; - 如需复用相同初始化逻辑,可封装为私有工厂方法,但勿在
@Mock字段声明处直接new A(b)(此时b尚未初始化)。
总之,@InjectMocks 是便利工具,而非必需范式。当构造逻辑与 Mock 行为强耦合时,回归显式、确定性的手动构造,反而是更稳健、更可维护的测试实践。










