Duration.between with mocked Instants

Viewed 49

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

enter image description here

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()
  }
1 Answers

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 have thread-local explicit scope. 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().

Related