Coroutines

MockK supports coroutines from first days, but during last year, the internal engine became more advanced, and few syntactic clauses were added as well.

First, what you need to remember is that all functions to work with coroutines in MockK are built of regular function + prefix co . For example every becomes coEvery , verify — coVerify , answers — coAnswers . Such functions are taking suspend lambdas instead of regular lambdas and allow to call suspend functions.

coEvery {

mock.divide(capture(slot), any())

} coAnswers {

slot.captured * 11

}

So from first sight, everything is very similar to regular mocking. But internally it is not as easy as it gets.

The first thing is that when the call to divide function gets intercepted it has implicit continuation parameter as the last argument on a byte-code level. Continuation is a callback to use when the result is ready. This means that the library needs to pass this continuation to the coAnswers clause, so it is not simply runBlocking the suspend lambda passed.

The second difference is that divide suspension function may decide to suspend. That means that internally it will return special suspension marker. When computations resume, divide is called again. As a result, each such resumed call will count as an additional invocation of the divide function. This means you need to take care of it during verification.

Understanding this internals is not a very pleasant job, but unfortunately, there is no other way to deal with it.

Verification timeout

When dealing with coroutines, people often forget that launch runs a job that executes in parallel and to verify results of such process you need one process to wait for another. This may be achieved with join . But then you need to explicitly pass the reference to Job everywhere.

To simplify writing such tests MockK supports verification with timeouts:

mockk<MockCls> {

coEvery { sum(1, 2) } returns 4 launch {

delay(2000)

sum(1, 2)

} verify(timeout = 3000) { sum(1, 2) }

}

Simply saying that the verify clause will exit, either if verification criteria are met or will throw an exception in case of timeout. That way you have no need explicitly to wait till the moment computation in launch is finished.

Verification confirmation & recording exclusions

verifyAll and verifySequence are two verification modes where an exclusive set of calls are verified. There is no possibility that something goes wrong and called more times than needed.

On one hand verifyOrder and simple verify do not work like that. On the other hand, they give you much more flexibility.

To address this difference, you can use a special verification confirmation call after all your verification blocks.

confirmVerified(object1, object2)

This will make sure that all calls for object1 and object2 were covered by verify clauses.

In case you have some less significant calls, you have the ability to exclude them from confirmation verification by excludeRecords construct.

excludeRecords { object1.someCall(andArguments) }

You can exclude a series of calls using argument matchers.

Varargs

Simple dealing with variable arguments was present in early MockK version, but from version 1.9.1 additional matchers were implemented.

Let me demonstrate this on a simple function that takes variable arguments:

interface VarargExample {

fun example(vararg sequence: Int): Int

}

Let’s mock it and see what are MockK possibilities:

val mock = mockk<VarargExample>()

First, it is possible to use it as simple arguments:

every { mock.example(1, 2, 3, more(4), 5) } returns 6

This means that if the variable argument array is of size 5 and it matches provided arguments, then the result of example function is 6.

It is of course not very flexible in case you need to deal with the variable length of variable arguments.

To overcome this limitation, MockK was enhanced with three matchers: anyVararg , varargAll and varargAny

every { mock.example(*anyVararg()) } return 1

This will simply match any possible variable argument array.

You can add any prefix or postfix of matchers.

every { mock.example(1, 2, *anyVararg(), 3) } return 4

Which will match at least 3 elements array that starts from 1 , 2 and ends with 3 .

To make more advanced matching varargAll allow providing a lambda that will set a condition that all arguments(except prefix and postfix) should be met.

every { mock.example(1, 2, *varargAll { it > 5 }, 9) } returns 10

This matches the following arguments arrays:

mock.example(1, 2, 5, 9) mock.example(1, 2, 5, 6, 9) mock.example(1, 2, 5, 6, 7, 9)

as well as:

mock.example(1, 2, 5, 5, 5, 9)

and so on.

Additionally in the scope of this lambda position and nArgs properties are available to allow more sophisticated matching expressions.

every { mock.example(1, *varargAll { nArgs > 5}) } returns 6

Which will match only if passed 6 or more arguments to example where the first argument is 1