Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mock Instant.now() without using Clock into Constructor or without Clock Object

Tags:

java

junit

java-8

I have below code in one of my methods

ZonedDateTime current = Instant.now().atZone(ZoneId.of(AMERICA_NEW_YORK));

I want to mock current in the JUnit test.

I tried with java.time.Clock but for this, I need to add it into the class constructor as my code is written into old versions of Spring and using XML based configuration this class cause issue because it requires constructor argument in the application-context.xml file if I use a constructor with Clock.

Is there any way to avoid constructor configuration and mock current in the above code.

Update

As per Pavel Smirnov's comments, I tried below but current still returning today's date but not the one which I am mocking.

ZonedDateTime exactOneDay = ZonedDateTime.parse("Sun Oct 21 12:30:00 EDT  2018", Parser); 
doReturn(exactOneDay).when(spyEmployeeHelper).getCurrentTime();
employee = getEmployees().get(0);
assertEquals(Integer.valueOf(1), employee.getNoticePeriod());
like image 323
ppb Avatar asked Mar 21 '19 20:03

ppb


3 Answers

Mockito based solution where code uses plain Instant.now()

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;

import java.time.Clock;
import java.time.Instant;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

public class MockInstantTest {

  private MockedStatic<Clock> clockMock;

  @BeforeEach
  public void setup() {
    mockInstant(1640000000); // set desired return value 2021-12-20T11:33:20Z
  }

  @AfterEach
  public void destroy() {
    clockMock.close();
  }

  private void mockInstant(long expected) {
    Clock spyClock = spy(Clock.class);
    clockMock = mockStatic(Clock.class);
    clockMock.when(Clock::systemUTC).thenReturn(spyClock);
    when(spyClock.instant()).thenReturn(Instant.ofEpochSecond(expected));
  }

  @Test
  void testWithMockedIstant() {
    // invoking Instant.now() will always return the same value
    assertThat(Instant.now().toString()).isEqualTo("2021-12-20T11:33:20Z");
  }
}

Solution Explained

Solution relays on the fact that Instant.now() invokes Clock.systemUTC().instant()

  • Clock is abstract so we spy for non static methods
  • clock.instant() is mocked to return the desired value
  • Clock.systemUTC() is static so we need mockStatic
  • using @Before/@After is required to close MockedStatic (alternatively you can use try(MockedStatic<Clock> clockMock = mockStatic(Clock.class)) {...})
like image 168
ezer Avatar answered Oct 19 '22 17:10

ezer


You can declare a function that returns ZoneDateTime:

public ZoneDateTime getCurrentTime () {
    return Instant.now().atZone(ZoneId.of(AMERICA_NEW_YORK));
}

and assign the result of that funciton to the current field:

ZonedDateTime current = getCurrentTime();

Now you can simply replace it with desired value using Mockito framework:

doReturn(yourValue).when(yourObject).getCurrentTime();
like image 22
Pavel Smirnov Avatar answered Oct 19 '22 17:10

Pavel Smirnov


When using Mockito you can easily mock like so:

    ZoneId zoneId = ZoneId.of("America/New_York");
    ZonedDateTime current = ZonedDateTime.now(zoneId);
    Timestamp timestamp = Timestamp.from(Instant.now());
   
    when(timestamp.toInstant()).thenReturn(Instant.from(current));

Adding Test for timeout example:

@Test
public void testForTimeout() throws InterruptedException {

    ZoneId zoneId = ZoneId.of("America/New_York");
    ZonedDateTime current = ZonedDateTime.now(zoneId);
    Timestamp timestampBeforeCall = Timestamp.from(Instant.now());

    // Call Class.method() or here instead we just introduce an artificial wait time :
    Thread.sleep(3000);

    Timestamp timestampAfterCall = Timestamp.from(Instant.now());
    long timeoutInMilliseconds = 2000;
    long diff = timestampAfterCall.getTime() - timestampBeforeCall.getTime();
    log.info(String.valueOf(diff));
    if(diff > timeoutInMilliseconds) {
        log.error("Call Timed Out!");
    }
}
like image 41
Thomas M Avatar answered Oct 19 '22 15:10

Thomas M