I am mocking Instants in my unit tests with mockito (org.mockito:mockito-core:4.7.0). The instants are mocked as expected (see screenshot).
Once I calculate the Duration of the 2 mocked Instants I get the following error:
Cannot read field "seconds" because "end" is null
I thougt that the problem is, that the whole Instant class is mocked, so I additionally passed Mockito.CALLS_REAL_METHODS so that the real implementation is used for the methods I do not mock:
val mock: MockedStatic[Instant] = mockStatic(classOf[Instant], Mockito.CALLS_REAL_METHODS)
Then I get the following error:
class org.mockito.internal.util.reflection.ReflectionMemberAccessor cannot access a member of class java.time.Instant (in module java.base) with modifiers "private static"
MyClass.scala
class ClassToTest() {
def run(): Duration = {
val now = Instant.now()
val now1 = Instant.now()
Duration.between(now1, now)
}
}
Test.scala
@Test
def test_duration(): Unit = {
val testee = new ClassToTest()
val timeMocked = Instant.parse("2022-08-31T08:55:48.200Z")
val mock: MockedStatic[Instant] = mockStatic(classOf[Instant])
try {
mock.when(() => Instant.now()).thenReturn(timeMocked)
val result = testee.run()
} finally if (mock != null) mock.close()
}
CodePudding user response:
Trying to be funny: “The problem is not the problem. The problem is your attitude about the problem”.
What I meant to say is, your problem is not the static call to now()
, but thinking that you should mock it.
From the Clock documentation:
Best practice for applications is to pass a
Clock
into any method that requires the current instant. A dependency injection framework is one way to achieve this:public class MyBean { private Clock clock; // dependency inject ... public void process(LocalDate eventDate) { if (eventDate.isBefore(LocalDate.now(clock)) { ... } } }
This approach allows an alternate clock, such as fixed or offset to be used during testing.
Now if you'll check the documentation for Instant.now()
you'll see:
Using this method will prevent the ability to use an alternate time-source for testing because the clock is effectively hard-coded.
public static Instant now() {
return Clock.systemUTC().instant();
}
Luckily, Instant
offers the alternative Instant.now(Clock clock):
Using this method allows the use of an alternate clock for testing. The alternate clock may be introduced using dependency injection.
public static Instant now(Clock clock) {
Objects.requireNonNull(clock, "clock");
return clock.instant();
}
By injecting a Clock
in your design:
class ClassToTest {
val clock: Clock = Clock.systemUTC()
def run(): Duration = {
val now = Instant.now(clock)
val now1 = Instant.now(clock)
Duration.between(now1, now)
}
}
You easily achieve what you want:
"test" should {
"should test duration" in {
val testee = new ClassToTest {
override val clock: Clock = mock[Clock]
}
val timeMocked = Instant.parse("2022-08-31T08:55:48.200Z")
when(testee.clock.instant()).thenReturn(timeMocked)
val result = testee.run()
result shouldBe Duration.ZERO
}
}
Of course, if you still want to mock the static call to now
, that is doable with PowerMockito, but I wouldn't go down that road, unless there are no better alternatives.
EDIT:
Since Mockito 3.4.0, this is also doable with Mockito, but their mocks are implemented differently. In this case, you need to mock Duration.between
instead of Instant.now
. The null
issue is because Duration.between
cannot calculate the duration between 2 mocks of Instant.now()
.