skip to content

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