skip to content

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

level: middleimportance: should knowfreq 50%

answer

  1. returns = constant, coAnswers = dynamic
  2. firstArg/secondArg/lastArg typed accessors
  3. self = the mock receiver
  4. block is suspend, can delay
  5. answers is the non-suspend twin

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.

solid answer

~50 s

coEvery { } returns x is a constant. coAnswers { } provides a suspend lambda that runs on every matching call, so you can: derive the result from arguments, call delay() to simulate latency, or vary behavior by call count. Inside the block you read arguments via the args list or the typed firstArg(), secondArg(), lastArg() helpers, and the original receiver via self. Because the block is suspend, you can await other suspend collaborators or use the virtual clock. Use it for stateful fakes (e.g., an in-memory store), latency simulation under runTest's virtual time, or computing responses (coAnswers { firstArg<Long>() * 2 }). For sequences of fixed values prefer returnsMany; reach for coAnswers only when you need logic or suspension. The non-suspend twin is answers; coAnswers is required when the answer body itself must call suspend functions.

code

kotlin · 5 lines
kotlin
coEvery { api.quote(any()) } coAnswers {
    val sku = firstArg<String>()
    delay(100)            // virtual time under runTest
    sku.length * 10       // result derived from the argument
}

go deeper

for a junior

Knows coAnswers exists for dynamic behavior versus a fixed returns value.

for a middle

Reads arguments via firstArg/lastArg and uses coAnswers to derive results or simulate latency.

for a senior

Builds stateful fakes with coAnswers and chooses returnsMany/andThen vs coAnswers appropriately, aware of per-call execution.

for a principal

Weighs hand-rolled coAnswers fakes against real fakes, and reasons about virtual-time delay semantics and argument-type safety in the answer scope.

## returns vs coAnswers - `coEvery { repo.get(any()) } returns Dto("x")` — **same value every time**, evaluated once. - `coEvery { repo.get(any()) } coAnswers { ... }` — a **suspend lambda re-run on each matching call**, producing a value dynamically. Choose `coAnswers` when behavior depends on inputs, must suspend (e.g., `delay`), or must change across invocations. ## What the block exposes The receiver of the lambda is MockK's `Call`/`MockKAnswerScope`, giving you: - `args` — `List<Any?>` of the actual arguments. - Typed accessors: `firstArg<T>()`, `secondArg<T>()`, `thirdArg<T>()`, `lastArg<T>()`. - `self` — the mock instance the call was made on. - `invocation` / `method` — metadata about the call. Because it's a **suspend** lambda, you may call `delay(...)`, await other suspend functions, etc. ```kotlin interface PriceApi { suspend fun quote(sku: String): Int } val api = mockk<PriceApi>() // 1. derive result from the argument coEvery { api.quote(any()) } coAnswers { val sku = firstArg<String>() sku.length * 10 } // 2. simulate latency under virtual time coEvery { api.quote("SLOW") } coAnswers { delay(500) // advanced by runTest's scheduler 99 } // 3. stateful fake val store = mutableMapOf<String, Int>() coEvery { api.quote(any()) } coAnswers { store.getOrPut(firstArg()) { store.size } } ``` ## coAnswers vs returnsMany vs andThen - Fixed sequence of values: `returnsMany listOf(a, b, c)` or `returns a andThen b`. - Throw then succeed: `throws e andThenAnswers { ... }`. - Anything requiring computation or suspension: `coAnswers`. ## Why not plain answers? `answers { }` is the non-suspend version. If the body must invoke a `suspend` function (or `delay`), only `coAnswers` compiles. For a suspend mocked function whose answer body needs no suspension, `answers` can still work, but `coAnswers` is the safe, idiomatic default. ## Pitfalls - `firstArg<T>()` with the wrong `T` throws a ClassCastException at call time — match the real signature. - A `delay` inside `coAnswers` only progresses under `runTest`/virtual time; on a real dispatcher it actually waits. - The block runs **per call**, so side effects (incrementing a counter) accumulate — usually what you want for a fake.

  • How do you read the second argument of the stubbed call inside coAnswers?
    Use secondArg<T>() (or args[1] cast to T). Typed accessors firstArg/secondArg/thirdArg/lastArg are the idiomatic way.
  • You need the stub to throw on the first call and succeed on the second. coAnswers?
    You can branch on a counter inside coAnswers, but throws e andThenAnswers { ... } (or returnsMany) is cleaner for fixed sequences.

returns is a vending machine with one fixed item; coAnswers is a barista who reads your order and makes a different drink each time.

saying these in an interview costs you the question

  • Saying returns can compute from arguments
  • Not knowing firstArg/lastArg accessors exist
  • Using answers when the body must call a suspend function
  • Assuming delay in coAnswers is free outside runTest's virtual time

context