How to mock Instant.now() in Kotlin?

105 Views Asked by At

How can I mock Instant.now() using mockK? This code throws error:

@Test
fun testStaticMock() {
  val instant = Instant.parse("2024-03-22T12:00:00.000Z")
  mockkStatic(Instant::class) {
    every { Instant.now() } returns instant
    val actual = Instant.now()
    assertEquals(instant, actual)
  }
}

Missing mocked calls inside every { ... } block: make sure the object inside the block is a mock

2

There are 2 best solutions below

1
marstran On BEST ANSWER

Not really answering your question here, but that's because I think it is bad practice to mock an Instant. You should instead use a Clock and pass it as an argument to your service/function. You can call Instant.now(clock) to get an instant from the Clock. A Clock makes your code much more testable, because you can control it better from your tests.

In your production code, you can use a real clock with Clock.system(ZoneId zone), Clock.systemDefault() or Clock.systemUTC().

In your tests, you can create a fixed-clock with Clock.fixed(Instant fixedInstant, ZoneId zone), and even make it tick with Clock.tick, Clock.tickMinutes or Clock.tickSeconds. This will make your tests more predictable, because they won't depend on the time they are run.

1
Joffrey On

First, I would like to reiterate @marstran's answer. You shouldn't mock this, you should instead inject a Clock into your production classes, and inject a test clock during tests.

If you really really want to go the mock route, then read on.


Answering the exact question, the every { ... } line should be outside the mockkStatic block:

val instant = Instant.parse("2024-03-22T12:00:00.000Z")
mockkStatic(Instant::class)
every { Instant.now() } returns instant


val actual = Instant.now()
assertEquals(instant, actual)

However, beware: with JDK16+ you might have problems. Your exact example is even mentioned on Mockk's website as a problematic one, that leads to this error:

java.time.format.DateTimeParseException: Text '2022-09-15T09:31:26.919Z' could not be parsed:
  Unable to make private static java.time.Instant java.time.Instant.create(long,int) accessible: 
  module java.base does not "opens java.time" to unnamed module @4501b7af

As a workaround, you will have to open modules for this to work (see the link above).