如何为含System.getenv的构造函数实现JUnit5全测试覆盖
JUnit5实现含System.getenv()代码的全测试覆盖方案
问题背景
需要为包含System.getenv()调用的Java代码实现100%JUnit5测试覆盖,但原代码中环境变量读取逻辑在构造函数内,导致测试配置无法提前生效,无法覆盖envWord == null ? "default" : envWord的两个分支。
原代码实现
ServiceToTest类
@Service public class ServiceToTest { private final CustomContainer customContainer; public ServiceToTest() { Object configuration = new Object(); String envWord = System.getenv("envword"); this.customContainer = new CustomContainer(configuration, envWord == null ? "default" : envWord); } public String getContainerName() { return customContainer.getContainerName(); } }
CustomContainer类
public class CustomContainer { @Getter String containerName; Object configuration; public CustomContainer(Object configuration, String containerName) { this.configuration = configuration; this.containerName = containerName; } }
尝试过的无效方案
曾使用ReflectionTestUtils、@SystemStubsExtension、Spy等方式,但由于构造函数执行时机早于测试配置,无法控制System.getenv()的返回值,导致无法覆盖空值分支。
重构方案(实现100%覆盖)
通过引入抽象接口解耦环境变量读取和对象创建逻辑,实现测试时的Mock控制。
1. CustomContainerFactory接口及实现
CustomContainerFactory接口
public interface CustomContainerFactory { CustomContainer create(Object configuration, String name); }
CustomContainerFactoryImpl实现类
@Service public class CustomContainerFactoryImpl implements CustomContainerFactory { @Override public CustomContainer create(Object configuration, String name) { return new CustomContainer(configuration, name); } }
2. EnvironmentAccessor接口及实现
EnvironmentAccessor接口
public interface EnvironmentAccessor { String getEnv(String name); }
EnvironmentAccessorImpl实现类
@Service public class EnvironmentAccessorImpl implements EnvironmentAccessor { @Override public String getEnv(String name) { return System.getenv(name); } }
3. 重构后的ServiceToTest类
@Service public class ServiceToTest { private final CustomContainer customContainer; public ServiceToTest(EnvironmentAccessor environmentAccessor, CustomContainerFactory customContainerFactory) { Object configuration = new Object(); String envWord = environmentAccessor.getEnv("anything"); this.customContainer = customContainerFactory.create(configuration, envWord == null ? "default" : envWord); } public String getContainerName() { return customContainer.getContainerName(); } }
优化后的测试类
@ExtendWith(MockitoExtension.class) class TestService { private static CustomContainer mockCustomContainer = mock(CustomContainer.class); private static CustomContainerFactory customContainerFactoryMock = mock(CustomContainerFactoryImpl.class); private static EnvironmentAccessor environmentAccessorMock = mock(EnvironmentAccessorImpl.class); private static ServiceToTest serviceToTest; @BeforeAll static void setup() { when(environmentAccessorMock.getEnv(anyString())).thenReturn("hi"); when(customContainerFactoryMock.create(any(), anyString())).thenReturn(mockCustomContainer); serviceToTest = new ServiceToTest(environmentAccessorMock, customContainerFactoryMock); } @Test void testGetContainerNameNotNull() { assertNotNull(serviceToTest.getContainerName()); } @Test void coverNullReturnFromGetEnv() { when(environmentAccessorMock.getEnv(anyString())).thenReturn(null); assertAll(() -> new ServiceToTest(environmentAccessorMock, customContainerFactoryMock)); } }
重构后通过Mock抽象接口,可分别控制环境变量的返回值,完整覆盖了分支逻辑,实现100%测试覆盖。
内容的提问来源于stack exchange,提问作者BugsOverflow
相关产品推荐
相关产品推荐

