I have MyClass
with
doSomething
- method to be testedgetYourStaticBool
- method to be mocked, contains System.getenv
method which cannot be mockedMY_STATIC_BOOL
- it has to be static and the value is provided by getYourStaticBool
MyClass:
public class MyClass {
private static boolean MY_STATIC_BOOL = getYouStaticBool();
public void doSomething() {
if (MY_STATIC_BOOL) {
//....
}
}
protected static boolean getYourStaticBool() {
return Optional.ofNullable(System.getenv("foo"))
.map(Boolean::parseBoolean)
.orElse(false);
}
}
Now I want to doSomething
so I have to mock getYourStaticBool
to set MY_STATIC_BOOL
on true.
@ExtendWith(MockitoExtension.class)
public class MyClassTest {
private MyClass myClass = new MyClass();
@BeforeAll
void static init() {
try (MockedStatic<MyClass> myClass = Mockito.mockStatic(MyClass.class)) {
myClass.when(() -> MyClass.getYourStaticBool())
.thenReturn(true);
}
}
@Test
void shouldDoSth() {
myClass.doSomething();
}
}
Unfortunately calling myClass.doSomething()
reveals that MY_STATIC_BOOL
is false. So the real getYourStaticBool
is called, not the mocked one.
Maybe should I use spy and mock static getYourStaticBool
? I can't use PowerMockito.
Mocking the method using mockStatic
will not work in this case, because the moment Mockito.mockStatic(MyClass.class)
is executed, the class is initialized and the method you would like to mock is called already to initialize the static field. If you wanted to mock the method call, the try
block would have to be around the tested code (see: for example here).
Since MY_STATIC_BOOL
is not final
, we can simply set its value using reflection (if it was final it would be also possible, yet not that easy, see: here or here).
I modified the tested code for the sake of the test:
public String doSomething() {
if (MY_STATIC_BOOL) {
return "Success";
}
return "Failure";
}
and here's the test:
@Test
void shouldDoSth() throws Exception {
var staticField = MyClass.class.getDeclaredField("MY_STATIC_BOOL");
staticField.setAccessible(true);
staticField.set(null, true);
var result = myClass.doSomething();
assertEquals("Success", result);
}
To avoid interfering with other tests, resetting the field with the original value would be advised (for example in @AfterAll
method or using try-finally
blocks).
Another option, which also works if the field is final
is using System Stubs library:
@ExtendWith(SystemStubsExtension.class)
class MyClassWithSystemStubsTest {
@SystemStub
EnvironmentVariables environment;
@Test
void theTest() {
environment.set("foo", "true");
MyClass myClass = new MyClass();
var result = myClass.doSomething();
assertEquals("Success", result);
}
}
Note that the environment should be set before creating the tested class object (myClass
), because the class (and the static field) is initialized then, so the instance cannot be created as a field in the test for this to work, unless you use:
@SystemStub
static EnvironmentVariables testWideVariables = new EnvironmentVariables("foo", "true");
(see docs in the link above)
You can find both tests described above in this GitHub repository - all tests pass.