非Spring Boot的Spring应用如何Mock静态final ApplicationContext做单元测试
解决方案
方案1:重构代码(优先推荐)
你当前的写法本身违反了Spring依赖注入的设计原则:类内部硬编码初始化ApplicationContext,主动拉取依赖,而非通过外部注入依赖,天生对单元测试不友好,同时还会存在上下文重复初始化、环境切换不灵活等问题。重构后可以从根本上解决测试问题:
- 不需要持有ApplicationContext的场景:直接注入依赖的Bean,完全解耦Spring上下文
public class MyClass { private final DependencyBean dependencyBean; // 构造器直接注入依赖,不需要感知ApplicationContext存在 public MyClass(DependencyBean dependencyBean) { this.dependencyBean = dependencyBean; } // 类的其他方法 }
单元测试时直接将Mock好的依赖传入构造器即可,不需要任何Spring相关的初始化逻辑。
2. 确实需要使用ApplicationContext的场景:通过构造器注入上下文,而非内部初始化
public class MyClass { private final ApplicationContext context; // 由外部传入ApplicationContext实例 public MyClass(ApplicationContext context) { this.context = context; // 从context中获取依赖的Bean } // 类的其他方法 }
单元测试时直接传入Mock好的ApplicationContext实例即可。
方案2:不修改业务代码,使用Mock工具修改静态final字段(临时适配方案)
如果因为历史遗留问题无法修改业务代码,可以利用Mock框架的能力修改静态final字段的值,绕过真实ApplicationContext的初始化逻辑,Mockito 3.4+ 和 PowerMock都支持该能力:
Mockito 3.4+ 实现示例
import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import static org.mockito.Mockito.*; public class MyClassTest { @Test void testMyClassMethod() { // 初始化Mock的上下文和依赖Bean ApplicationContext mockContext = mock(ApplicationContext.class); DependencyBean mockBean = mock(DependencyBean.class); when(mockContext.getBean(DependencyBean.class)).thenReturn(mockBean); // 替换AnnotationConfigApplicationContext的构造逻辑,返回Mock的上下文 try (MockedStatic<AnnotationConfigApplicationContext> mockedAppContext = mockStatic(AnnotationConfigApplicationContext.class)) { mockedAppContext.when(() -> new AnnotationConfigApplicationContext(AppContext.class)) .thenReturn(mockContext); // 此时初始化MyClass,内部static字段会被赋值为Mock的context MyClass myClass = new MyClass(); // 执行你的测试逻辑 } } }
PowerMock 实现示例
import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import static org.mockito.Mockito.*; @RunWith(PowerMockRunner.class) @PrepareForTest(MyClass.class) public class MyClassTest { @Test public void testMyClassMethod() throws Exception { // 初始化Mock的上下文和依赖Bean ApplicationContext mockContext = mock(ApplicationContext.class); DependencyBean mockBean = mock(DependencyBean.class); when(mockContext.getBean(DependencyBean.class)).thenReturn(mockBean); // 反射修改static final字段的值 Field contextField = MyClass.class.getDeclaredField("context"); contextField.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(contextField, contextField.getModifiers() & ~Modifier.FINAL); contextField.set(null, mockContext); // 初始化MyClass执行测试 MyClass myClass = new MyClass(); // 执行你的测试逻辑 } }
内容的提问来源于stack exchange,提问作者Shridha Jalihal
相关产品推荐
相关产品推荐

