2

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

user871611
  • 3,307
  • 7
  • 51
  • 73
  • Non answer: don't mock static stuff. Ever. Not with PowerMock(ito), not with the Mockito itself. Why? Because "mocking static" is always coming with the potential of exactly that: something changing here, and your code breaking. Seriously: the *only* reason to mock static elements in Java is when you have to test existing legacy code that you can't modify or test in any other way. But for any new piece of code understand: the need to mock static stuff is caused by: your production code being hard to test. It is always better to make the code EASY to test instead. – GhostCat Jul 08 '22 at 06:40
  • Thanks for your thoughts. For my real test, some calculations are done with `Instant.now()` and I don't see any other possibility to do it some other way. – user871611 Jul 08 '22 at 06:47
  • 1
    Well, this is where dependency injection comes in. Your production code should RECEIVE a Clock object from *somewhere*, and then you go `now = Instant.now(thatClock)` ... see https://stackoverflow.com/questions/27067049/unit-testing-a-class-with-a-java-8-clock – GhostCat Jul 08 '22 at 06:59
  • 1
    That could be as simple as: have a second non public constructor that takes that Clock object. And the default public constructor just creates an ordinary clock, but your tests use the *other* ctor and pass in a Clock object that you have full control over. – GhostCat Jul 08 '22 at 06:59
  • Thanks for the detailed information. Unfortunately this is legacy code and I'm not able to change it. – user871611 Jul 08 '22 at 09:51
  • Then I am glad that you got a solution that works for you. But remember: usage for old code is okay, but try to absolutely avoid it when writing new tests for new code. – GhostCat Jul 17 '22 at 17:32

1 Answers1

2

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);
    }
}
Lesiak
  • 22,088
  • 2
  • 41
  • 65