如何模拟含System.getEnv赋值字段的类?单元测试报错解决
解决依赖System.getEnv的类单元测试初始化错误问题
问题描述
我正在为一个通过System.getEnv("...")获取环境变量的类编写单元测试,运行测试时出现初始化错误。尝试模拟System类但没有效果,请问怎么解决才能正常运行测试?
错误栈信息
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at org.mockito.internal.util.reflection.ReflectionMemberAccessor.lambda$newInstance$0(ReflectionMemberAccessor.java:29) at org.mockito.internal.util.reflection.ReflectionMemberAccessor.newInstance(ReflectionMemberAccessor.java:29) at org.mockito.internal.util.reflection.ReflectionMemberAccessor.newInstance(ReflectionMemberAccessor.java:20) at org.mockito.internal.util.reflection.ModuleMemberAccessor.newInstance(ModuleMemberAccessor.java:42) at org.mockito.internal.util.reflection.FieldInitializer$ParameterizedConstructorInstantiator.instantiate(FieldInitializer.java:287) at org.mockito.internal.util.reflection.FieldInitializer.acquireFieldInstance(FieldInitializer.java:146) at org.mockito.internal.util.reflection.FieldInitializer.initialize(FieldInitializer.java:91) at org.mockito.internal.configuration.injection.ConstructorInjection.processInjection(ConstructorInjection.java:48) at org.mockito.internal.configuration.injection.MockInjectionStrategy.process(MockInjectionStrategy.java:68) at org.mockito.internal.configuration.injection.MockInjectionStrategy.relayProcessToNextStrategy(MockInjectionStrategy.java:91) at org.mockito.internal.configuration.injection.MockInjectionStrategy.process(MockInjectionStrategy.java:71) at org.mockito.internal.configuration.injection.MockInjection$OngoingMockInjection.apply(MockInjection.java:88) at org.mockito.internal.configuration.DefaultInjectionEngine.injectMocksOnFields(DefaultInjectionEngine.java:26) at org.mockito.internal.configuration.InjectingAnnotationEngine.injectCloseableMocks(InjectingAnnotationEngine.java:118) at org.mockito.internal.configuration.InjectingAnnotationEngine.processInjectMocks(InjectingAnnotationEngine.java:61) at org.mockito.internal.configuration.InjectingAnnotationEngine.process(InjectingAnnotationEngine.java:48) at org.mockito.MockitoAnnotations.openMocks(MockitoAnnotations.java:81) at org.mockito.internal.framework.DefaultMockitoSession.(DefaultMockitoSession.java:43) at org.mockito.internal.session.DefaultMockitoSessionBuilder.startMocking(DefaultMockitoSessionBuilder.java:83) at org.mockito.junit.jupiter.MockitoExtension.beforeEach(MockitoExtension.java:153) at java.util.ArrayList.forEach(ArrayList.java:1259) at java.util.ArrayList.forEach(ArrayList.java:1259) Suppressed: java.lang.NullPointerException at org.mockito.junit.jupiter.MockitoExtension.afterEach(MockitoExtension.java:184) at java.util.ArrayList.forEach(ArrayList.java:1259) ... 47 more Caused by: java.lang.NullPointerException at com.sixsense.orchestration.minionlib.service.PreviewCommonService.(PreviewCommonService.java:29) ... 77 more
解决方案
方法1:解耦环境变量依赖(推荐)
直接修改PreviewCommonService类,避免在构造方法中直接调用System.getEnv,改用构造方法注入的方式传入环境变量值,既符合依赖注入原则,也方便测试:
public class PreviewCommonService { private final String targetEnvValue; // 测试友好的构造方法,直接接收环境变量值 public PreviewCommonService(String targetEnvValue) { this.targetEnvValue = targetEnvValue; } // 保留原有无参构造,兼容生产环境逻辑 public PreviewCommonService() { this(System.getEnv("YOUR_TARGET_ENV_KEY")); } // 业务方法... }
测试时直接传入模拟值:
@Test void testPreviewCommonService() { PreviewCommonService service = new PreviewCommonService("mock_env_value"); // 执行测试逻辑 }
方法2:通过反射修改System环境变量
Mockito无法直接模拟System的静态方法(因为System是final类),可以通过反射修改System内部存储环境变量的映射:
import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; public class EnvTestHelper { // 设置指定环境变量 public static void setEnv(String key, String value) throws Exception { Field envField = System.class.getDeclaredField("env"); envField.setAccessible(true); Map<String, String> currentEnv = (Map<String, String>) envField.get(null); Map<String, String> newEnv = new HashMap<>(currentEnv); newEnv.put(key, value); envField.set(null, newEnv); } // 测试后恢复原环境变量 public static void resetEnv() throws Exception { Field initializedField = System.class.getDeclaredField("initialized"); initializedField.setAccessible(true); initializedField.set(null, false); Class<?> systemClass = Class.forName("java.lang.System"); java.lang.reflect.Method initMethod = systemClass.getDeclaredMethod("initializeSystemClass"); initMethod.setAccessible(true); initMethod.invoke(null); } }
测试类中使用:
@BeforeEach void setUp() throws Exception { EnvTestHelper.setEnv("YOUR_TARGET_ENV_KEY", "mock_value"); } @AfterEach void tearDown() throws Exception { EnvTestHelper.resetEnv(); } @Test void testService() { PreviewCommonService service = new PreviewCommonService(); // 执行测试逻辑 }
方法3:用PowerMock模拟静态方法
如果无法修改目标类,可以使用PowerMock来模拟System.getEnv的静态行为:
- 引入PowerMock依赖(Maven示例):
<dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency>
- 编写测试类:
import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) @PrepareForTest(System.class) public class PreviewCommonServiceTest { @Test void testService() { // 模拟System.getEnv返回指定值 PowerMockito.mockStatic(System.class); PowerMockito.when(System.getEnv("YOUR_TARGET_ENV_KEY")).thenReturn("mock_env_value"); PreviewCommonService service = new PreviewCommonService(); // 执行测试逻辑 } }
内容的提问来源于stack exchange,提问作者Aakash_Deep
相关产品推荐
相关产品推荐

