使用@InjectMocks与@MockBean时静态/非静态对象的行为解析
Spring Boot测试中@InjectMocks与@MockBean静态/非静态组合的行为解析
代码结构
MyService类
@Service @ToString public class MyService{ Dependency dependency; public MyService(Dependency dependency) { System.out.println("started MyService constructor"); this.dependency = dependency; System.out.println("ended MyService constructor with dependency = " + this.dependency); } public void myMethod() { System.out.println("printing in myMethod : " + dependency.someMethod()); } public void setDependency(Dependency dependency) { this.dependency = dependency; System.out.println("called setter with dependency = " + this.dependency); } }
Dependency类
@Component public class Dependency { public String someMethod() { return "calling dependency someMethod"; } }
测试类
@SpringBootTest @ContextConfiguration(classes = {MyService.class}) class MyServiceTest { @InjectMocks MyService myService; @MockBean Dependency dependency; @Value("${someproperty}") private String someProperty; @BeforeEach void beforeEach() { System.out.println("beforeEach"); } @Test void test1() { System.out.println("test1 start"); when(dependency.someMethod()).thenReturn("calling mock dependency 1 someMethod"); myService.myMethod(); System.out.println("test1 end"); } @Test void test2() { System.out.println("test2 start"); when(dependency.someMethod()).thenReturn("calling mock dependency 2 someMethod"); myService.myMethod(); System.out.println("test2 end"); } }
四种静态/非静态组合的执行结果
- 场景1:MyService静态、Dependency静态:MyService构造函数执行时dependency为null,第一个测试失败;setDependency被调用,dependency设为Mock对象,第二个测试正常运行。
- 场景2:MyService静态、Dependency非静态:MyService构造函数执行时dependency为null,两个测试均失败。
- 场景3:MyService非静态、Dependency静态:MyService构造函数首次执行时dependency为null,第一个测试失败;第二次构造函数执行时dependency为Mock对象,第二个测试正常运行。
- 场景4:MyService非静态、Dependency非静态:MyService两次构造函数执行时dependency均为null,第一个测试失败,第二个测试正常运行。
行为差异解析
要搞懂这些差异,得结合Spring测试上下文的初始化顺序、静态成员的生命周期和Mockito的注入逻辑三个关键点来看:
核心基础
- 静态成员:属于类而非实例,一旦初始化就会被所有测试方法共享,不会随测试类实例的重建而重置。
- @MockBean:会把Mock对象注册到Spring测试上下文,替换原有的Dependency Bean。静态@MockBean的实例在整个测试周期内不变;非静态@MockBean会在每个测试方法前创建新的Mock实例并更新上下文。
- @InjectMocks注入顺序:优先用构造函数注入,若构造函数依赖的Bean不存在,则尝试Setter注入或字段注入,注入时机在测试类实例初始化后、
@BeforeEach执行前。
场景1:MyService静态、Dependency静态
- 测试启动时,Spring上下文先初始化MyService Bean,但此时静态Dependency Mock还没被注册到上下文,所以构造函数只能传入null,导致test1调用
myMethod时触发空指针失败。 - 第一个测试结束后,Spring上下文复用(因为两个Mock都是静态的,实例不会变),Mockito完成Setter注入,把静态Dependency Mock注入到静态MyService实例中。第二个测试执行时,MyService的dependency已经是Mock对象,所以能正常运行。
场景2:MyService静态、Dependency非静态
- 静态MyService Bean在测试启动时创建,此时非静态Dependency Mock还未初始化,构造函数传入null。
- 每个测试方法执行前,非静态Dependency会生成新的Mock实例并更新上下文,但静态MyService是类级单例,构造函数只会执行一次,且Mockito默认不会对静态字段做重复注入更新,所以两次测试中MyService的dependency始终是null,全部失败。
场景3:MyService非静态、Dependency静态
- 第一个测试执行前,Spring上下文初始化,静态Dependency Mock还没完成注册时,非静态MyService就被创建了,构造函数传入null,test1调用
myMethod失败。 - 第一个测试结束后,Spring上下文复用,第二个测试执行前重新创建非静态MyService实例,此时静态Dependency Mock已经在上下文里了,构造函数能直接获取到Mock对象,所以test2正常运行。
场景4:MyService非静态、Dependency非静态
- 第一个测试执行前,Spring上下文初始化顺序是先创建MyService Bean,再注册Dependency Mock,所以构造函数传入null。虽然Mockito后续会通过Setter注入Mock,但这个时机晚于test1的
myMethod调用,导致test1失败。 - 第二个测试执行前,Spring上下文已经初始化完成,Dependency Mock提前存在于上下文中,MyService创建时要么通过构造函数拿到Mock,要么Mockito的Setter注入及时完成,所以test2能正常运行。
内容的提问来源于stack exchange,提问作者Sarthak
相关产品推荐
相关产品推荐

