Title
t

tim

10/11/2020, 8:47 PM
Hello, i'm trying to mock Instant but I can't seem to get it to work. My approach:
object MockkInstant {
    var value: Instant = Instant.now()

    fun mock() {
        mockkStatic(Instant::class)

        every {
            Instant.now()
        } answers {
            value.minusMillis(0) // line 22 from stack trace below
        }
    }

    fun clear() {
        clearStaticMockk(Instant::class)
    }

    fun step(duration: Duration = 1.seconds) {
        value = value.add(duration) // extension function defined elsewhere
    }
}
Now in my tests i'm trying to do this:
"fixes calls to Instant.now()" {
    val a = Instant.now()
    delay(1)
    val b = Instant.now()
    a.shouldBe(b)
}
But I'm getting an error from the
answers
block:
Empty list doesn't contain element at index 0.
java.lang.IndexOutOfBoundsException: Empty list doesn't contain element at index 0.
	at kotlin.collections.EmptyList.get(Collections.kt:35)
	at kotlin.collections.EmptyList.get(Collections.kt:23)
	at io.mockk.MockKAnswerScope.getValueAny(API.kt:3834)
	at io.mockk.MockKAnswerScope.getValue(API.kt:2188)
	at io.glimpseprotocol.testing.mockk.MockkInstant$mock$2.invoke(MockkInstant.kt:22)
	at io.glimpseprotocol.testing.mockk.MockkInstant$mock$2.invoke(MockkInstant.kt:13)
	at io.mockk.MockKStubScope$answers$1.invoke(API.kt:2092)
	at io.mockk.MockKStubScope$answers$1.invoke(API.kt:2069)
	at io.mockk.FunctionAnswer.answer(Answers.kt:19)
...
I've seen other people using returns, but afaik that returns the same Instant object whereas I want to create a new one each time Instant.now() is called. Any suggestions?
l

LeoColman

10/11/2020, 10:05 PM
Are you using Kotest?
t

tim

10/12/2020, 7:13 AM
Yes using KoTest ... this looks great i will check that out 🙏
Hmm doesn't quite appear to have the features I'm trying to accomplish above with mockk. For example being able to 'step' forward/backword in time is a requirement as I'm walking through a dataset and want to tightly control the value of Instant.now(). From what I can tell with kotest constant intant functions, they appear to be more about fixing Instant.now() with in a block whereas I want to fix it across my running test and then step forward, and then step forward again. I guess i could wrap each step in withConstantInstant and then manually create the instant for each step... just seems a bit verbose especially if say i wanted to test it over 50 steps
l

LeoColman

10/12/2020, 7:35 AM
Kotest uses mockk inside itself
You can take a look at the implementation and copy how the listeners do
(for the instant part specifically)
t

tim

10/12/2020, 8:06 AM
Ahh k
If they're providing constant time my hunch is they'll be using
returns
rather than
answers
will check out thier source
ty
Managed to get 'answers' to work correctly:
@OptIn(ExperimentalTime::class)
object MockkInstant {
    var step: Duration = 0.seconds
    private lateinit var base: Instant

    fun mock(from: Instant = Instant.now()) {
        base = from
        mockkStatic(Instant::class)

        every {
            Instant.now()
        } answers {
            base.add(step)
        }
    }

    fun clear() {
        clearStaticMockk(Instant::class)
    }

    fun step(duration: Duration = 1.seconds) {
        step += duration
    }
}