3

I am writing unit tests using TestNG. The problem is when I mock System.currentTimeMillis, it returns the actual value instead of the mocked one. Ideally, it should return 0L , but it returns the actual value. What should I do to proceed?

class MyClass{

    public void func1(){
       System.out.println("Inside func1");
       func2();
    }

    private void func2(){
        int maxWaitTime = (int)TimeUnit.MINUTES.toMillis(10);
        long endTime = System.currentTimeMillis() + maxWaitTime; // Mocking not happening
        while(System.currentTimeMillis() <= endTime) {
                System.out.println("Inside func2");
        }
    }
}

@PrepareForTest(System.class)
class MyClassTest extends PowerMockTestCase{
   private MyClass myClass;
   
   @BeforeMethod
   public void setup() {
     MockitoAnnotations.initMocks(this);
     myclass = new MyClass();
   }
   @Test    
   public void func1Test(){
      PowerMockito.mockStatic(System.class)
      PowerMockito.when(System.currentTimeMillis()).thenReturn(0L);
      myclass.func1();
   }
}
Dawson Smith
  • 473
  • 1
  • 6
  • 15

3 Answers3

6

Make a package constructor that you can pass in a java.time.Clock

class MyClass{ 
    private Clock clock;
    public MyClass() {
        this.clock = Clock.systemUTC();
    } 
    // for tests 
    MyClass(Clock c) {
        this.clock = c;
   } 

Then mock that for tests, and use this.clock.instant() to get the clock's time

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
0

You need to add the annotation @RunWith(PowerMockRunner.class) to the class MyClassTest.

Nevertheless, I would advise to refactor the code to use java.time.Clock, instead of mocking.

Edgar Domingues
  • 930
  • 1
  • 8
  • 17
-1

Instead of using PowerMock, you can use Mockito which also has a mockStatic method

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>3.9.0</version>
    <scope>test</scope>
</dependency>

See this answer for an example with LocalDate

Here is how it would look like in your case

try(MockedStatic<System> mock = Mockito.mockStatic(System.class, Mockito.CALLS_REAL_METHODS)) {
    doReturn(0L).when(mock).currentTimeMillis();
    // Put the execution of the test inside of the try, otherwise it won't work
}

Notice the usage of Mockito.CALLS_REAL_METHODS which will guarantee that whenever System is invoked with another method, it will execute the real method of the class

Yassin Hajaj
  • 21,337
  • 9
  • 51
  • 89
  • MockedStatic is not available. Intellij is unable to import any such class – Dawson Smith Apr 25 '21 at 13:54
  • It's not recommended to depend on actual system time in tests, especially when dealing with timezones – OneCricketeer Apr 25 '21 at 13:54
  • @OneCricketeer But I have to. It's not possible on my end to change the actual code – Dawson Smith Apr 25 '21 at 13:56
  • @DawsonSmith you should use `Mockito` for this – Yassin Hajaj Apr 25 '21 at 14:01
  • @YassinHajaj But how? is it present in TestNG? – Dawson Smith Apr 25 '21 at 14:02
  • No, you can add the dependency, I just added it in the post – Yassin Hajaj Apr 25 '21 at 14:03
  • 2
    This would be awesome, but unfortunately `MockedStatic` does not allow mocking `System` methods as you will get a `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`. See [this GitHub issue comment](https://github.com/mockito/mockito/issues/1013#issuecomment-666994541) for the rationale. – mrts Jul 23 '21 at 18:54