Search code examples
mockitojava-17

Static mock failing on JDK 17


The same code worked perfectly with JDK 11. Switching to JDK 17 makes the test fail, since Instant.now() returns null.

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

@Test
void mockStatic() {
    final Instant instantExpected = Instant.parse("2022-03-10T10:15:30Z");
    final Clock spyClock = spy(Clock.class);
    when(spyClock.instant()).thenReturn(instantExpected);
    try (final MockedStatic<Clock> clockMock = mockStatic(Clock.class)) {
      clockMock.when(Clock::systemUTC).thenReturn(spyClock);
      final Instant now = Instant.now();
      assertEquals(instantExpected, now);
    }
}

Running on Windows 10, Mockito 4.6.1, Eclipse Temurin 17.0.2.8


Solution

  • The difference stems from the fact that JDK 17 no longer calls Clock.systemUTC() in implementation of Instant.now()

    Please compare:

    JDK 17:

    public static Instant now() {
        return Clock.currentInstant();
    }
    

    JDK 15:

    public static Instant now() {
        return Clock.systemUTC().instant();
    }
    

    If you insist on mocking static methods, you could stub Instant.now(), not Clock.systemUTC() - thus you don't rely on the implementation of Instant.now()

    As discussed in the comments in under your post, this is not the recommended approach - class Clock was designed specifically to make time-handling code easier to test, use a Clock in your code instead of calling Instant.now()

    @Test
    void mockStaticClock() {
        final Instant instantExpected = Instant.parse("2022-03-10T10:15:30Z");
    
        try (final MockedStatic<Instant> instantMock = mockStatic(Instant.class)) {
            instantMock.when(Instant::now).thenReturn(instantExpected);
            final Instant now = Instant.now();
            assertEquals(instantExpected, now);
        }
    }