7

I have a test case that mock a static method of java.lang.System class:

@Test
fun `getLocalTime()`() {
    // Arrange
    val staticMock = Mockito.mockStatic(System::class.java)
    Mockito.`when`(System.currentTimeMillis()).thenReturn(1000L)

    // Action
    val res = deviceTimeProvider.getLocalTime()

    // Assert
    Truth.assertThat(res).isEqualTo(1000L)

    staticMock.close()
}

But when I run the test, I got this error:

org.mockito.exceptions.base.MockitoException: It is not possible to mock static methods of java.lang.System to avoid interfering with class loading what leads to infinite loops

Why does this happen? How can I mock methods of java.lang.System class?

nhoxbypass
  • 9,695
  • 11
  • 48
  • 71

3 Answers3

7

While Mockito since 3.4.0 version allows mocking static methods it is not allowed to mock the Thread and System static methods, see this comment on github

Finally note that Mockito forbids mocking the static methods of System (and Thread). Those methods are to much cemented into class loading which happens in the same thread. At some point, we might add instrumentation to class loading to temporarily disable the static mocks within it to make mocking these classes, too, where we also would need to disable their intensification properties. You can however easily mock Instant.now().

kasptom
  • 2,363
  • 2
  • 16
  • 20
  • Thank you. Are there any workaround solution for this? – nhoxbypass Oct 06 '20 at 03:03
  • I think, just like @Michal Drozd suggests: `PowerMockito` would do the trick but using the `System` methods directly is not a good option. See [this answer for the similar problem](https://stackoverflow.com/a/17398362/4880379) – kasptom Oct 06 '20 at 08:24
  • Year, I've already hide `System.currentTimeMillis()` under an interface (`DeviceTimeProvider` in the code above). But I still want to unit-test it also.... – nhoxbypass Oct 06 '20 at 08:28
1

If you like ugly solutions you can still mock System with PowerMockito

@PrepareForTest(System.class)
public class TestCase {
    @BeforeClass
    public void setup() {
        PowerMockito.mockStatic(System.class);
        PowerMockito.when(System.currentTimeMillis()).thenReturn(1000L);
    }
...

But I would avoid mocking System classes if possible. You can still wrap it in method and mock this method.

Michal Drozd
  • 1,311
  • 5
  • 13
  • 26
1

To mock the static methods of java.lang.System class with the help of Mockito.

  1. Create an interface i.e ISystem.java
public interface ISystem {

    String getProperty(String name);

    Long getCurrentTimeInMillis();

}

2- Create the implementation class of ISystem interface i.e ISystemImpl.java

public class ISystemImpl implements ISystem {

    @Override
    public String getProperty(final String name) {
        return System.getProperty(name);
    }

    @Override
    public Long getCurrentTimeInMillis() {
        return System.currentTimeMillis();
    }

}

3- Use Isystem.java inside your DeviceTimeProvider.java class.

public class DeviceTimeProvider {

   @NonNull private final ISystem mISystem;

   public DeviceTimeProvider(ISystem iSystem){
       mIsystem = iSystem;
   }

   public Long getLocalTime(){
       return mIsystem.getCurrentTimeInMillis()
   }

}

4- Now finally mock the ISystem interface inside your test class.

public class DeviceTimeProviderTest {

   private ISystem mISystem;
   private DeviceTimeProvider sut;

   @Before
   public setup(){
       mIsystem = mockito.mock(ISystem.class)
       sut = new DeviceTimeProvider(mISystem);
   }

   @Test
   public void getDeviceLocalTime(){
       Long expectedTime = 1000L;
       mockit.when(mISystem.getCurrentTimeInMillis()).thenReturn(expectedTime);
       
       Long actualTime = sut.getLocalTime();

       Assert.assertEquals(actualTime, expectedTime);
   }

}

OUTPUT

enter image description here

JunaidKhan
  • 654
  • 7
  • 15
  • It is a bad practice to adjust application code for tests. – stove Aug 09 '21 at 12:21
  • Without adding interface layer on top of the java.lang.System is not possible to mock it in your test using Mockito. – JunaidKhan Aug 09 '21 at 12:49
  • True, but you can use other libraries, e.g. PowerMockito or SystemStubs, but you should never, ever design your production code to fit tests. In this case you add redundant interface and class just for tests - but the two classes will be in your production code.... Its infinitely better to add another testing library than to add even a single interface if its just to accomodate tests. – stove Aug 10 '21 at 07:43