You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用@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的注入逻辑三个关键点来看:

核心基础

  1. 静态成员:属于类而非实例,一旦初始化就会被所有测试方法共享,不会随测试类实例的重建而重置。
  2. @MockBean:会把Mock对象注册到Spring测试上下文,替换原有的Dependency Bean。静态@MockBean的实例在整个测试周期内不变;非静态@MockBean会在每个测试方法前创建新的Mock实例并更新上下文。
  3. @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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 02:09:51