skip to content

MockK lets you finish a stub for a suspend function with either `answers { }` or `coAnswers { }`. What is the concrete difference between the two blocks, and in whose coroutine does a `coAnswers` body execute?

level: seniorimportance: should knowfreq 42%

answer

  1. answers = plain lambda, coAnswers = suspend lambda
  2. body runs inline in the caller's coroutine
  3. delay inside coAnswers is cancellable
  4. Thread.sleep in answers = blocked thread
  5. coAndThen chains a second suspending answer

basics

~20 s

coAnswers takes a suspending lambda, so it may call delay or other suspend functions; the plain answers block is non-suspending and cannot. The coAnswers body runs inline in the caller's coroutine, so the caller's dispatcher and cancellation apply to it.

solid answer

~50 s

Both terminators compile after `coEvery`, so people assume they are interchangeable — they are not. `answers { }` takes an ordinary lambda: it can compute a result from the arguments, but any suspend call inside it fails to compile (*Suspension functions can be called only within coroutine body*). `coAnswers { }` declares its block **suspend**, so the body may `delay`, await another suspend collaborator, or call a suspending helper. The second half matters for behaviour: when the stubbed suspend function is invoked, MockK runs the `coAnswers` body inline in the calling coroutine — it does not launch anything of its own and does not switch dispatchers. So a `delay` inside the block genuinely suspends the caller and is cancellable by the caller's job or `withTimeout`, and whatever scheduler the caller runs on is the one that sees the suspension. Rule of thumb: use `answers` when the result is a pure function of the arguments, `coAnswers` when the fake behaviour itself needs to suspend.

code

kotlin · 14 lines
kotlin
// suspending answer: may delay / call other suspend functions
coEvery { repo.load(any()) } coAnswers {
    delay(50)
    Data(firstArg<String>())
}

// plain answer: pure computation only
coEvery { repo.load(any()) } answers {
    // delay(50) here would not compile
    Data(firstArg<String>())
}

// different behaviour per invocation
coEvery { repo.load("a") } coAnswers { delay(10); Data("slow") } coAndThen { Data("fast") }

go deeper

for a junior

Know that coAnswers takes a suspending block so you can call delay or other suspend functions inside it, and answers does not.

for a middle

Add the compile-time reason (suspend lambda vs ordinary lambda) and pick correctly: pure computation from arguments uses answers, suspending behaviour uses coAnswers.

for a senior

Explain that the body runs inline in the caller's coroutine — so dispatcher, suspension and cancellation belong to the code under test — and contrast with Thread.sleep in a plain answers block.

for a principal

Turn it into guidance: suspending answers are for modelling latency and interleaving at a seam, they must stay cancellable, and tests should never buy fake slowness with blocked threads.

## Two terminators that look interchangeable After recording a stub for a suspend function with `coEvery { … }`, MockK accepts several terminators. Two of them take a lambda that computes the result at call time: `answers { }` and `coAnswers { }`. Because both compile in that position, candidates often reach for whichever their IDE offers first. The distinction is a Kotlin-level one with a real behavioural consequence. ## Difference one: what the block may contain `answers { }` declares an ordinary function type. Inside it you can read the call's arguments and compute a value, but you cannot call a suspend function — the compiler rejects it with *Suspension functions can be called only within coroutine body*. `coAnswers { }` declares the same block as a `suspend` lambda. That single modifier is why it exists: it lets the fake behaviour itself suspend. Inside a `coAnswers` body you may `delay`, call another suspend collaborator (real or mocked), read from a `Channel`, `await` a `Deferred`, and so on. This is the same reason the whole `co*` family exists — `coEvery`, `coVerify`, `coJustRun`, `coAnswers`. None of them are a different mocking engine; they are the suspend-lambda spellings of the same DSL, needed because Kotlin will not let a suspend call appear in a non-suspending lambda. ## Difference two: where the body runs MockK does not own a coroutine scope, does not launch a coroutine of its own, and does not switch dispatchers to run your answer. When production code calls the stubbed suspend function, the answer body is invoked inline as part of that call, on the caller's continuation. Three consequences follow, and they are the part interviewers are actually probing: 1. **Cancellation propagates.** A `delay` inside `coAnswers` is a real, cancellable suspension point belonging to the caller's job. If the code under test wraps the call in `withTimeout`, the timeout genuinely cancels the stub. That is what makes `coAnswers { delay(…); value }` a usable way to simulate a slow dependency. 2. **The caller's dispatcher is in play.** Whatever context the calling coroutine runs in is the context the answer body sees. If the test drives the caller with a scheduler that provides virtual time, the suspension is observed by that scheduler rather than by wall-clock sleeping. 3. **Ordering is deterministic.** Because nothing is launched in the background, the answer completes before the stubbed call returns; there is no race between the stub and the assertions that follow. Contrast that with faking slowness by calling `Thread.sleep` inside a plain `answers` block: that blocks the carrier thread, ignores cancellation entirely, and costs real wall-clock time. It is the classic wrong version of the same idea. ## Where each is the right choice - **`answers`** — the result is a pure function of the arguments or of test-local state: echo an argument back, look up a value in a map the test owns, build a response from the request. No suspension needed, so do not pay for one. - **`coAnswers`** — the fake behaviour must itself suspend: simulate latency, yield so another coroutine can interleave, delegate to a real suspending helper, or emit into a channel the test also reads. A useful corollary: putting `coAnswers` on a **non-suspend** member is legal but pointless. There is no caller continuation for the body to suspend into, so do not build behaviour that depends on suspension there — keep `coAnswers` for suspend members and use `answers` for blocking ones. ## Chaining Both terminators have sequential forms, so consecutive calls can behave differently: after `coAnswers { … }` you can chain `coAndThen { … }` to give the *next* invocation another suspending answer. That is how you model "first attempt is slow and fails, retry succeeds" without touching the production code. ## What to say in an interview Lead with the mechanism, not the syntax: `coAnswers` exists because the answer body is a suspend lambda, and MockK executes it in the caller's coroutine rather than in one of its own — so suspension, dispatcher and cancellation all belong to the code under test. Everything else (which one compiles where, when to prefer each) follows from that sentence.

  • If you need a stub to sleep, why is delay inside coAnswers better than Thread.sleep inside answers?
    `delay` inside a `coAnswers` body is a suspension point in the caller's coroutine: it releases the thread, respects the caller's dispatcher, and is cancelled when the caller's job or a `withTimeout` around it cancels. `Thread.sleep` inside a plain `answers` block blocks the carrier thread, ignores cancellation completely, and always costs real wall-clock time, which makes tests slow and makes timeout behaviour untestable.
  • Is it valid to use coAnswers on a function that is not suspend?
    It compiles, but it is the wrong tool. There is no caller continuation for the block to suspend into, so behaviour that relies on suspension has no well-defined meaning there. Use `answers` for blocking members and reserve `coAnswers` for suspend members, so the terminator also documents which kind of member is being stubbed.

saying these in an interview costs you the question

  • Saying answers and coAnswers are the same thing with a different name.
  • Claiming MockK launches the answer on its own dispatcher or scope.
  • Thinking a delay inside coAnswers cannot be cancelled by the caller's withTimeout.
  • Reaching for Thread.sleep to simulate a slow suspend collaborator.
  • Assuming coAnswers is required for every suspend stub, even when the answer is a pure computation.

context