A single MockK mock stands in for an interface that has both suspend and ordinary functions. How do you stub and verify each kind, and can one verification block cover calls of both kinds?
answer
- one mock, two DSL entry points
- co* = suspending lambda, plain = ordinary lambda
- suspend lambda may hold blocking calls too
- coVerifySequence spans mixed calls
- Flow-returning function is not suspend
basics
~20 sOne mock handles both: use every/verify for ordinary members and coEvery/coVerify for suspend ones. The co-variants also accept ordinary calls, so coVerifySequence can cover a mixed run; the plain verify block cannot contain a suspend call at all.
solid answer
~50 sNo special setup is needed — a `mockk<T>()` stubs suspend and blocking members on the same instance; the `co` prefix is about the **DSL block**, not about the mock. - Ordinary members: `every { … } returns …` and `verify { … }`. - Suspend members: `coEvery { … } returns …` and `coVerify { … }`. The asymmetry is worth stating explicitly: `every`/`verify` blocks are plain lambdas, so a suspend call inside them does not compile. `coEvery`/`coVerify` blocks are suspend lambdas, and a suspend lambda may contain ordinary calls too — so the `co` variants work for blocking members as well. That is what lets `coVerifySequence { … }` assert an ordered run that interleaves a suspend fetch with a blocking close. A frequent trap: a function returning `Flow` is usually **not** suspend, so it is stubbed with `every { … } returns flowOf(…)`; only the collecting side suspends.
code
kotlin · 18 linesinterface OrderClient {
suspend fun fetch(id: String): Order
fun stream(): Flow<Order> // NOT suspend
fun close()
}
val client = mockk<OrderClient>()
every { client.stream() } returns flowOf(order)
justRun { client.close() }
coEvery { client.fetch("42") } returns order
runBlocking { service.run() }
coVerifySequence { // suspend + blocking calls in one ordered block
client.fetch("42")
client.stream()
client.close()
}go deeper
Know that one mock covers both kinds and that suspend members need the co-prefixed DSL.
Explain the mechanic: the prefix is about whether the DSL lambda may contain a suspend call, so co-variants also accept blocking calls; know that Flow-returning functions are not suspend.
Use the one-way flexibility deliberately — coVerifySequence for mixed ordered runs — and keep the plain form on blocking members so the setup documents which calls suspend.
Set the convention: prefix tracks the call site, not the technology, and mixed-kind ordered verifications should be rare because they encode a lot of implementation order into the test.
## The mock does not care There is no such thing as a "coroutine mock" in MockK. `mockk<OrderClient>()` produces one object that can answer every member of the type — `suspend fun fetch(id: String): Order`, `fun stream(): Flow<Order>` and `fun close()` alike. Stubs for all three live in the same mock's record. What changes between them is only which DSL entry point you use to *record* the stub or the verification, and that choice is dictated by Kotlin's rules about suspend calls, not by MockK. ## The rule in one sentence `every`, `verify`, `verifyOrder`, `verifySequence`, `justRun` take ordinary lambdas; `coEvery`, `coVerify`, `coVerifyOrder`, `coVerifySequence`, `coJustRun` take suspending lambdas. A suspend call may only appear inside a suspending lambda — otherwise the compiler says *Suspension functions can be called only within coroutine body*. Therefore: - suspend member → you **must** use the `co` variant; - ordinary member → either works, because a suspending lambda is perfectly happy to contain non-suspending calls. That one-way flexibility is the key mechanic. It is not a loophole; it is why a mixed verification block is possible at all. ## Stubbing a mixed collaborator Write each stub with the matching entry point: ``` every { client.stream() } returns flowOf(order) justRun { client.close() } coEvery { client.fetch("42") } returns order ``` Using `coEvery` for the blocking members too would compile and behave identically. Most teams still prefer the plain form for blocking members, because the prefix then documents which members suspend — useful information when reading a large setup block. ## Verifying a mixed interaction For a single unordered assertion, verify each kind with its own call, or put both inside one `coVerify`. When the *order* across kinds matters — "fetch first, then close" — you need one block that can contain both calls, and only the `co` form can: ``` coVerifySequence { client.fetch("42") client.stream() client.close() } ``` The reverse is impossible: no plain `verifySequence` can mention `fetch`, because that call cannot be written in a non-suspending lambda. ## The Flow trap The single most common mistake on mixed interfaces is assuming that anything coroutine-flavoured is suspend. A function declared `fun stream(): Flow<Order>` is an ordinary function that returns a cold stream object; nothing suspends until something collects it. So it is stubbed with `every { client.stream() } returns flowOf(order)`. Candidates who reach for `coEvery` here are not wrong at runtime (it compiles and works, since the block accepts ordinary calls), but they reveal that they think the prefix tracks "coroutines" rather than "this call site suspends". The same applies in reverse: `suspend fun fetch()` needs `coEvery` even though nothing about it mentions `Flow` or dispatchers. ## Calling the mock in the test Stubbing a suspend member is not enough to *invoke* it — the invocation still has to happen inside a coroutine, so the test body needs a coroutine builder or a suspending test function. That is a property of Kotlin, not of MockK: the mock itself imposes no requirement about scope, dispatcher or scheduler. ## Things that trip people up - **"I need two mocks, one for suspend and one for blocking."** No — one mock, two DSL entry points. - **"`verify` should work, MockK is just being awkward."** It is a compile error from Kotlin, not a MockK restriction, and it disappears the moment you use `coVerify`. - **"`coEvery` everywhere is fine."** It works, but it stops the prefix from carrying information; a reader can no longer tell from the setup which members suspend. - **Mixing kinds in an *ordered* verification and reaching for `verifyOrder`.** Only the `co` form can express the mixed sequence. ## The takeaway The `co` prefix is a statement about the lambda you are writing, not about the mock, not about the collaborator, and not about coroutines in general. Read every stub as: *does this call site suspend?* If yes, prefix it. If no, either form works and the plain one documents intent better.
- Is it harmful to use coEvery and coVerify for every member, suspend or not?Functionally no — a suspending lambda can contain ordinary calls, so the stubs and verifications behave exactly the same. The cost is readability: the prefix stops signalling which members actually suspend, so a reader of a long setup block has to check the interface. Most teams keep the plain form for blocking members precisely to preserve that signal.
saying these in an interview costs you the question
- Believing you need a separate mock or special mockk() option for suspend functions.
- Stubbing a Flow-returning function with coEvery because it "is coroutines".
- Thinking verify { } refusing a suspend call is a MockK limitation rather than a Kotlin compile rule.
- Claiming an ordered assertion cannot mix suspend and blocking calls.