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

Spring Boot JUnit测试:不使用PowerMockito模拟System.getenv

问题

我需要为直接调用System.getenv("mongodb_url")的MyService编写JUnit测试,但测试时该方法返回null,希望在不使用PowerMockito的前提下设置环境变量以通过测试。以下是现有代码:

现有代码

Service类

@Service
public class MyService{

    @Autowired
    MongoClient mongoClient;

    public void save(){
        String mongoUri= System.getenv("mongodb_url");
        if(mongoUri!=null){
            List<String> databaseNames = mongoClient.listDatabaseNames().into(new ArrayList<>());
            for (String dbName : databaseNames) {
                log.info(dbName+"database name");
            }
        }
    }
}

测试类

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {
    DefaultTestService.class})
public class test {

    @Mock
    Environment environment;
    @InjectMocks
    MyService myservice;

    @Test
    public void test(){
        when(System.getenv("mongodb_url")).thenReturn("mongodb://localhost:27017");
        myservice.save();
    }
}

Gradle依赖

implementation 'junit:junit:4.13.1'
解决方案

不用PowerMockito的话,优先推荐重构代码解耦依赖的方式,也可通过反射临时修改环境变量,以下是具体可行方案:

方案一:重构Service类,依赖Spring Environment(推荐)

直接调用System.getenv会让代码耦合性高、难以测试,改为依赖Spring的Environment接口,测试时可轻松mock该依赖:

修改后的Service类

@Service
public class MyService{

    @Autowired
    MongoClient mongoClient;
    
    @Autowired
    Environment environment; // 注入Spring环境对象

    public void save(){
        // 通过Environment获取环境变量
        String mongoUri= environment.getProperty("mongodb_url");
        if(mongoUri!=null){
            List<String> databaseNames = mongoClient.listDatabaseNames().into(new ArrayList<>());
            for (String dbName : databaseNames) {
                log.info(dbName+"database name");
            }
        }
    }
}

修改后的测试类

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyServiceTest { // 规范类名,首字母大写

    @Mock
    MongoClient mongoClient;
    @Mock
    Environment environment;
    @InjectMocks
    MyService myService;

    @Test
    public void testSave() {
        // mock环境变量返回值
        when(environment.getProperty("mongodb_url")).thenReturn("mongodb://localhost:27017");
        // mock MongoClient的listDatabaseNames方法,避免真实调用
        List<String> mockDbNames = Arrays.asList("test_db", "dev_db");
        when(mongoClient.listDatabaseNames()).thenReturn(new MockMongoCursor<>(mockDbNames));

        myService.save();

        // 验证业务逻辑执行
        verify(mongoClient, times(1)).listDatabaseNames();
    }
}

方案二:反射临时修改环境变量(仅适用于无法重构的场景)

通过反射修改System类中的环境变量映射,测试后需恢复原变量,避免影响其他测试用例:

测试类代码

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyServiceTest {

    @Mock
    MongoClient mongoClient;
    @InjectMocks
    MyService myService;

    private String originalMongoUrl;

    @Before
    public void setup() throws Exception {
        // 保存原环境变量值
        originalMongoUrl = System.getenv("mongodb_url");
        // 设置测试用的环境变量
        setEnvironmentVariable("mongodb_url", "mongodb://localhost:27017");
    }

    @After
    public void teardown() throws Exception {
        // 恢复原环境变量状态
        if (originalMongoUrl == null) {
            removeEnvironmentVariable("mongodb_url");
        } else {
            setEnvironmentVariable("mongodb_url", originalMongoUrl);
        }
    }

    @Test
    public void testSave() {
        List<String> mockDbNames = Arrays.asList("test_db");
        when(mongoClient.listDatabaseNames()).thenReturn(new MockMongoCursor<>(mockDbNames));

        myService.save();

        verify(mongoClient, times(1)).listDatabaseNames();
    }

    // 反射设置环境变量的工具方法
    private void setEnvironmentVariable(String key, String value) throws Exception {
        Class<?> systemClass = Class.forName("java.lang.System");
        Field envField = systemClass.getDeclaredField("env");
        envField.setAccessible(true);
        Map<String, String> env = (Map<String, String>) envField.get(null);
        env.put(key, value);

        // 更新系统缓存,保证读取一致性
        Field unsafeField = systemClass.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        Object unsafe = unsafeField.get(null);
        Method putVolatileMethod = unsafe.getClass().getMethod("putObjectVolatile", Object.class, long.class, Object.class);
        Field offsetField = systemClass.getDeclaredField("envOffset");
        offsetField.setAccessible(true);
        putVolatileMethod.invoke(unsafe, systemClass, offsetField.getLong(null), env);
    }

    // 反射移除环境变量的工具方法
    private void removeEnvironmentVariable(String key) throws Exception {
        Class<?> systemClass = Class.forName("java.lang.System");
        Field envField = systemClass.getDeclaredField("env");
        envField.setAccessible(true);
        Map<String, String> env = (Map<String, String>) envField.get(null);
        env.remove(key);

        // 更新系统缓存
        Field unsafeField = systemClass.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        Object unsafe = unsafeField.get(null);
        Method putVolatileMethod = unsafe.getClass().getMethod("putObjectVolatile", Object.class, long.class, Object.class);
        Field offsetField = systemClass.getDeclaredField("envOffset");
        offsetField.setAccessible(true);
        putVolatileMethod.invoke(unsafe, systemClass, offsetField.getLong(null), env);
    }
}

方案三:使用Spring Boot的@TestPropertySource(Spring环境专属)

如果测试基于Spring Boot环境,可通过@TestPropertySource设置属性,同时需要修改Service类从Spring属性取值:

修改后的Service类

@Service
public class MyService{

    @Autowired
    MongoClient mongoClient;
    
    @Value("${mongodb_url}") // 直接注入属性值
    private String mongoUri;

    public void save(){
        if(mongoUri!=null){
            List<String> databaseNames = mongoClient.listDatabaseNames().into(new ArrayList<>());
            for (String dbName : databaseNames) {
                log.info(dbName+"database name");
            }
        }
    }
}

测试类

@RunWith(SpringRunner.class)
@SpringBootTest
@TestPropertySource(properties = {"mongodb_url=mongodb://localhost:27017"})
public class MyServiceTest {

    @Mock
    MongoClient mongoClient;
    @InjectMocks
    MyService myService;

    @Test
    public void testSave() {
        List<String> mockDbNames = Arrays.asList("test_db");
        when(mongoClient.listDatabaseNames()).thenReturn(new MockMongoCursor<>(mockDbNames));

        myService.save();

        verify(mongoClient).listDatabaseNames();
    }
}

补充:完善Gradle依赖

原依赖缺少Spring Boot测试和Mockito相关依赖,需补充:

dependencies {
    implementation 'junit:junit:4.13.1'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.mockito:mockito-core:3.12.4'
}

内容的提问来源于stack exchange,提问作者Vijay

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 00:32:36