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

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());


Solution 1:[1]

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)) {...})

Solution 2:[2]

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();

Solution 3:[3]

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!");
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Pavel Smirnov
Solution 3