skip to content

Coroutine Mocking (coEvery / coVerify)

Suspend functions need coEvery and coVerify rather than their blocking counterparts, with coAnswers for a suspending answer block. Forgetting the co prefix is the first mistake people make mocking a coroutine-based collaborator.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

You have a mock with a suspend function and you write every { repo.load() } returns data — it won't compile. Why, and what do you use instead?

level: juniorimportance: must knowfreq 75%

answer

  1. every lambda is not suspend
  2. coEvery for suspend stubs
  3. coVerify mirrors verify
  4. tail (returns/throws) is identical
  5. wrap calls in runTest

basics

~10 s

every can only call ordinary functions. A suspend function must be called from a coroutine, so MockK gives you coEvery, which can run suspend calls inside its stub block. Use coEvery for suspend functions.

solid answer

~40 s

MockK's every { } lambda is a normal (non-suspend) lambda, so you can't invoke a suspend function inside it — the compiler rejects the call because suspend functions require a coroutine/suspend context. MockK provides coEvery { } whose lambda is itself suspend, so calling repo.load() (a suspend fun) inside it compiles and records the stub. The matching verification is coVerify { } (vs verify { }). The chained part after the block — returns, throws, answers — is identical to every; only the block needs to be suspend-capable. Rule of thumb: same function for stubbing, but co-prefixed (coEvery, coVerify, coAnswers) whenever the mocked function is declared with suspend.

code

kotlin · 7 lines
kotlin
val repo = mockk<UserRepo>()
coEvery { repo.load(1) } returns User("Ann")

@Test fun t() = runTest {
    assertEquals("Ann", repo.load(1).name)
    coVerify { repo.load(1) }
}

go deeper

for a junior

Knows that suspend functions need coEvery instead of every and that the call must happen in a coroutine.

for a middle

Explains the suspend-lambda compiler constraint and pairs coEvery with coVerify, using runTest for the call site.

for a senior

Articulates why the API is split rather than overloaded and can mix every/coEvery cleanly on one mock.

for a principal

Frames the co-prefixed API as a deliberate, opt-in coroutine boundary marker and reasons about how it composes with structured-concurrency test scaffolding.

## The core problem A function declared with the `suspend` keyword can only be called from another `suspend` function or from a coroutine builder (`launch`, `async`, `runTest`, etc.). The compiler enforces this. MockK's `every { ... }` takes a **plain lambda** (not suspend). So inside that lambda you cannot call a `suspend` function — the compiler errors with something like *"Suspend function 'load' should be called only from a coroutine or another suspend function."* ## The fix: the co-prefixed API MockK ships **suspend-aware twins** for exactly this case: - `coEvery { }` — stub a suspend function (the lambda is `suspend`). - `coVerify { }` — verify a suspend function was called. - `coAnswers { }` — provide a dynamic suspend answer. Everything *after* the block is shared with the non-co API: `returns`, `returnsMany`, `throws`, `answers`. ```kotlin interface UserRepo { suspend fun load(id: Long): User } val repo = mockk<UserRepo>() // WRONG — won't compile, load() is suspend // every { repo.load(1) } returns User("Ann") // RIGHT coEvery { repo.load(1) } returns User("Ann") @Test fun loads() = runTest { val u = repo.load(1) // call from a coroutine assertEquals("Ann", u.name) coVerify { repo.load(1) } // suspend-aware verify } ``` ## Why not just make `every` suspend? `every` is used for the vast majority of non-suspend stubs; keeping its lambda non-suspend keeps the common case simple. The co-variants are opt-in and signal at the call site that you're dealing with a coroutine boundary. ## Key takeaways - `suspend` collaborator ⇒ use the `co`-prefixed function. - The tail (`returns`, `throws`, `answers`) is unchanged. - Run the test body inside `runTest` so you actually have a coroutine to call the mock from.

  • If a class has both suspend and non-suspend functions, can you mix every and coEvery on the same mock?
    Yes. Use every for the plain functions and coEvery for the suspend ones; they target the same mock object independently.
  • Does coEvery require any extra MockK dependency?
    No. coEvery/coVerify/coAnswers are part of core MockK; you only need a coroutines test runner like kotlinx-coroutines-test for runTest.

every is a phone that only does voice; coEvery is the same phone that also handles video calls — same dialing, extra capability.

saying these in an interview costs you the question

  • Claiming every works for suspend functions if you add a return type
  • Saying you must wrap the stub body in launch instead of using coEvery
  • Thinking coEvery needs a separate MockK artifact
  • Believing returns/throws differ between every and coEvery

context

open as a page

Walk through stubbing and verifying a suspend repository call in a runTest block. What's the difference between coVerify and verify, and what happens if you use the wrong one?

level: middleimportance: must knowfreq 70%

basics

~20 s

Use coEvery to stub the suspend call and coVerify to check it ran, all inside runTest. verify can't call a suspend function in its block, so it won't compile for suspend functions — you must use coVerify.

open as a page

When would you choose coAnswers over coEvery { } returns, and what does the answer block give you access to?

level: middleimportance: should knowfreq 50%

basics

~20 s

Use coAnswers when the stubbed suspend function needs dynamic behavior — computing a result from the arguments, suspending/delaying, or returning different values per call. returns gives a fixed value; coAnswers runs a suspend block each time.

open as a page

On a relaxed mock, you call a suspend function you never stubbed. What does it return, and how does this interact with coEvery and coVerify?

level: seniorimportance: should knowfreq 40%

basics

~20 s

A relaxed mock auto-returns a sensible default (0, false, empty, or a child relaxed mock) even for suspend functions, so unstubbed coEvery isn't required. You can still coEvery to override and coVerify to check calls.

open as a page

How do you capture the argument passed to a suspend collaborator and verify the call within a deadline? Explain CapturingSlot with coVerify and the timeout option.

level: seniorimportance: nice to knowfreq 35%

basics

~20 s

Use a slot() with capture() in coEvery or coVerify to grab the actual argument, then assert on slot.captured. For async work, coVerify(timeout = ms) waits up to that many milliseconds for the call to happen before failing.

open as a page