skip to content

Inside MockK's `answers { }` block, how do you read the arguments the caller actually passed, and what are `firstArg()`, `arg<T>(n)` and `lastArg()` really doing?

level: middleimportance: must knowfreq 50%

answer

  1. answers { } runs per call, at call time
  2. firstArg/secondArg/thirdArg/lastArg + arg<T>(n), ZERO-based
  3. reified T = unchecked cast ⇒ ClassCastException at call time
  4. echo stub: answers { firstArg() }
  5. raw args list + nArgs for generic answers

basics

~20 s

The block runs at call time with an answer scope exposing the invocation. Use firstArg<T>(), secondArg<T>(), thirdArg<T>(), lastArg<T>() or arg<T>(n) for a zero-based index. Each is an indexed read of the argument list plus an unchecked cast to T, so a wrong T fails at that line.

solid answer

~50 s

`answers { }` registers a lambda that MockK invokes **on every matching call**, with a receiver — the answer scope — that carries the invocation. From it you read the arguments: ```kotlin every { formatter.format(any(), any()) } answers { "${firstArg<String>()}:${secondArg<Int>()}" } ``` `firstArg`, `secondArg`, `thirdArg` and `lastArg` are conveniences over `arg<T>(n)`, which is **zero-based**: `firstArg()` is `arg(0)`. All of them do the same two things — index into the recorded argument list, then cast to the reified type parameter. The cast is unchecked at the DSL level, so specifying the wrong type produces a ClassCastException at the moment the answer runs, inside the code under test, which can look like a production bug until you read the trace. The scope also exposes the raw `args` list and `nArgs`. Use the typed accessors by default; drop to `args` only for genuinely generic answers. This is how you build pass-through stubs — `answers { firstArg() }` — and answers computed from input rather than fixed.

code

kotlin · 9 lines
kotlin
val repo = mockk<UserRepo>()
every { repo.save(any()) } answers { firstArg() }   // returns what it was given

val rates = mockk<RateTable>()
every { rates.lookup(any(), any()) } answers {
    val from = firstArg<String>()
    val to = secondArg<String>()
    if (from == to) 1.0 else 0.9
}

go deeper

for a junior

Show you can write an echo stub with answers { firstArg() } and know the block runs on each call with the real arguments.

for a middle

Name the full accessor set including arg<T>(n) and its zero-based indexing, and explain that the type parameter is a cast applied at call time.

for a senior

Talk about the failure modes — ClassCastException surfacing inside the code under test, nullable arguments, silent off-by-one — and about choosing answers versus returns versus capture.

for a principal

Set a norm for how much logic belongs in a stub at all, and why assertions inside answer blocks are dangerous when production code catches broadly.

## What an answer block is Every terminator on a MockK stub registers an *answer*: an object that produces a result for an invocation. `returns x` registers a constant one. `answers { … }` registers a **lambda** one — code that MockK executes each time a matching call arrives. Two consequences that people miss: - The block runs **at call time**, not at stub-definition time. It sees the actual arguments of the actual call, and it re-runs for every matching call. - The block's value is the return value of the mocked function, so its type must fit the function's return type. ## The receiver: the answer scope The lambda has a receiver object (MockK's answer scope) which wraps the invocation and exposes it in typed form. The argument surface is: | Accessor | Meaning | |---|---| | `arg<T>(n)` | argument at **zero-based** index `n`, cast to `T` | | `firstArg<T>()` | `arg<T>(0)` | | `secondArg<T>()` | `arg<T>(1)` | | `thirdArg<T>()` | `arg<T>(2)` | | `lastArg<T>()` | the final argument, cast to `T` | | `args` | the raw `List<Any?>` of arguments | | `nArgs` | how many arguments there are | The type parameter is reified and applied as a cast. Kotlin will often infer it from context — `answers { firstArg() }` compiles when the function returns `String` — so you can omit it, at the price of a less obvious failure if the inference is wrong. Writing it explicitly, `firstArg<String>()`, documents the intent. ## The failure mode to know ```kotlin every { repo.save(any()) } answers { firstArg<Long>() } // arg is actually a User ``` Nothing complains at stub time. The exception fires the first time production code calls `save`, on the line inside the answer block, and it surfaces as a `ClassCastException` propagating out of the mocked call. Candidates who have not hit this waste real time believing the code under test is broken. The same applies to index mistakes: `arg` is zero-based, so `arg<Int>(1)` is the **second** parameter. Off-by-one here yields either a class-cast failure or, worse, a plausible-but-wrong value when adjacent parameters share a type. A nullable argument must be read as a nullable type — `firstArg<String?>()` — or the cast to the non-null type fails when the caller passed null. ## What it is good for **Pass-through / echo stubs.** The commonest use by far. A repository whose `save` returns the entity it was given: ```kotlin every { repo.save(any()) } answers { firstArg() } ``` This keeps the test honest: whatever the service builds flows back, so downstream assertions inspect the real object rather than a hard-coded stand-in. **Input-dependent answers** where a matcher-per-value would be noisy: ```kotlin every { rates.for(any()) } answers { if (firstArg<String>() == "USD") 1.0 else 0.9 } ``` **Invoking a callback the caller passed**, when the collaborator's contract is "call me back": ```kotlin every { client.load(any(), any()) } answers { secondArg<(String) -> Unit>().invoke("payload") } ``` **Side effects for recording**, e.g. appending to a list the test owns — a lighter alternative to a capture slot when you only need the values. ## When *not* to use it If the answer is constant, `returns` says so more clearly. If you only want to *inspect* what was passed, argument capture is the purpose-built tool and keeps assertions out of the stub. And avoid putting test assertions inside the block: an assertion that fails there throws from inside the code under test, where a `try/catch` in production code may swallow it and turn a real failure into a green test. ## Relationship to the rest of the answer API The same block form appears as `andThenAnswer { }` for later entries in a sequence, and as `coAnswers { }` when the stubbed function is suspending and the block itself needs to suspend. The scope and its accessors are identical in all three. ## Checklist - Runs per call, at call time, with the real arguments. - `arg(n)` is zero-based; `firstArg` = index 0; `lastArg` = final argument. - The type parameter is an unchecked cast — wrong type ⇒ ClassCastException inside the test subject. - Nullable arguments need a nullable type parameter. - Prefer `returns` for constants, capture for inspection, `answers` for computation.

  • A stub uses firstArg<Long>() but the parameter is a User. When and how does that fail?
    Not at stub time — the DSL only records a lambda. It fails the first time the mocked function is called, with a ClassCastException raised on that line inside the answer block and propagating out of the mocked call into the code under test. It commonly gets misread as a production bug until you notice the frame is inside the test's answer lambda.
  • How is an answers block different from capturing an argument with a slot?
    An answers block computes the return value at call time and can use the arguments to do it; a capture stores the argument so the test can assert on it afterwards. Use capture when the goal is inspection, because assertions inside a stub run inside the code under test and can be swallowed by a catch block there. Use answers when the return value genuinely depends on the input.
  • Does arg(1) mean the first or the second parameter?
    The second. arg is zero-based, so arg(0) is equivalent to firstArg() and arg(1) to secondArg(). Off-by-one errors here are especially nasty when neighbouring parameters share a type, because the cast succeeds and the stub silently returns a value derived from the wrong argument.

saying these in an interview costs you the question

  • Believing the answers block is evaluated once when the stub is defined.
  • Treating arg(n) as one-based.
  • Assuming the type parameter is checked, so a wrong type would be caught at compile or stub time.
  • Reading a nullable argument with a non-null type parameter.
  • Putting assertions inside the answer block instead of capturing and asserting outside.

context