Home > Back-end >  Duration.between with mocked Instants
Duration.between with mocked Instants

Time:09-02

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

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

  • Related