Search code examples
javaunit-testingjunitmockito

Mock a method in the class under test without PowerMockito


I have MyClass with

  • public doSomething - method to be tested
  • protected static getYourStaticBool - method to be mocked, contains System.getenv method which cannot be mocked
  • static boolean MY_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.


Solution

  • 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.