skip to content

In MockK, how does `andThenAnswer` differ from `andThen`, and when do you need a computed answer for the second and later calls of the same stub?

level: middleimportance: should knowfreq 28%

answer

  1. andThen = constant entry; andThenAnswer = lambda entry
  2. block sees the same scope: firstArg, args, self, callOriginal()
  3. use when the later value depends on the call or on mutable test state
  4. computed tail keeps recomputing after exhaustion
  5. fresh exception per call ⇒ throw inside andThenAnswer

basics

~20 s

andThen appends a constant value to the sequence; andThenAnswer appends a lambda that runs at call time with the answer scope, so it can read the arguments, compute a result or call the original. Use it whenever a later step must depend on that call's input, not on a value known up front.

solid answer

~60 s

Both append an entry to the same answer sequence; they differ in what the entry is. - `andThen value` — a constant, decided when the stub is written. - `andThenAnswer { … }` — a block evaluated when that entry is reached, with the same receiver `answers { }` has: `firstArg()`, `arg<T>(n)`, `args`, `self`, `callOriginal()`. ```kotlin every { cache.get(any()) } returns null andThenAnswer { store[firstArg<String>()] } ``` The first lookup misses; every later lookup is computed from the key that call actually passed — impossible with a constant, because the key is not known at stub time. The usual triggers are: the later value depends on the argument, on state the test mutates between calls, or on the real implementation via `callOriginal()`. A block is also how you make a later step fail with a freshly built exception (`andThenAnswer { throw IOException(...) }`), which keeps the stack trace at the call site rather than at the setup line. Sequence rules are unchanged: entries are consumed in order, and the last one repeats once exhausted.

code

kotlin · 8 lines
kotlin
val store = mutableMapOf("a" to 1, "b" to 2)
val cache = mockk<Cache>()

every { cache.get(any()) } returns null andThenAnswer { store[firstArg<String>()] }

cache.get("a")  // null (cold)
cache.get("a")  // 1
cache.get("b")  // 2

go deeper

for a junior

Know that andThen takes a value and andThenAnswer takes a block that runs when that call happens.

for a middle

Explain that both append to one sequence and that the block form gets the full answer scope, so later entries can depend on the arguments or on test state.

for a senior

Add the sharper points: a computed tail re-runs after exhaustion, throwing inside the block gives a fresh exception with a call-site trace, and callOriginal() makes "fake once then behave normally" trivial.

for a principal

Note when a multi-block sequence has stopped being a stub and become a script, and set the threshold at which the team writes a small fake instead.

## One sequence, two kinds of entry A MockK stub can hold an ordered list of answers. Every terminator and chain step appends to that list, and each entry is an *answer object*. The distinction that matters here is constant versus computed: | Step | Entry it appends | |---|---| | `returns v` / `andThen v` | constant value, fixed at stub time | | `throws e` / `andThenThrows e` | constant throw of that instance | | `answers { }` / `andThenAnswer { }` | lambda evaluated when that entry is used | So `andThenAnswer` is to `andThen` exactly what `answers` is to `returns`: the block form, one position later in the sequence. It receives the same answer scope, with the argument accessors (`firstArg`, `secondArg`, `arg<T>(n)`, `lastArg`, `args`, `nArgs`), the invocation metadata (`self`, `method`), and `callOriginal()`. ## Why a later entry would need to be computed ### The value depends on the argument A cache that misses once and then serves whatever key is asked for cannot be expressed with constants, because the keys are chosen by the code under test: ```kotlin every { cache.get(any()) } returns null andThenAnswer { store[firstArg<String>()] } ``` ### The value depends on state the test mutates Because the block runs at call time, it sees the *current* value of anything it closes over. A test can arrange "first call sees the old world, later calls see the new one" by flipping a variable between phases: ```kotlin var flagEnabled = false every { flags.isOn("beta") } returns false andThenAnswer { flagEnabled } ``` A constant `andThen` would have frozen the value at stub time. ### The later step should delegate to the real implementation On a spy or a patched object, `andThenAnswer { callOriginal() }` means "fake the first call, then behave normally" — a compact way to test warm-up or first-call-fails logic. ### The later step should throw a freshly constructed exception `andThenThrows ex` stores one instance and rethrows it, so its stack trace points at the setup line and any mutation on it is shared. `andThenAnswer { throw IOException("attempt ${...}") }` builds a new exception per call, with a trace rooted at the real call site and a message that can include the arguments. ### The step should have a side effect Appending to a list, incrementing a counter the test owns, or invoking a callback argument all require a block. Keep such side effects to recording; assertions inside an answer block are risky because they throw from inside the code under test, where a broad catch can swallow them. ## Rules that do not change - **Order.** Entries are consumed one per matching call, in the order written. - **Exhaustion.** The last entry repeats forever; if the last entry is a block, the block is re-executed on each further call, so a computed final entry keeps producing fresh values rather than freezing the first one it produced. That is a genuinely useful difference from a constant tail. - **Matcher scope.** The counter belongs to the stub selected by matchers; calls matching a different stub do not advance it. - **Re-stubbing.** A later `every` on the same call installs a new sequence and abandons the position of the old one. ## Suspending functions When the stubbed function suspends, the recording must be made with the coroutine-aware form, and the block form used for later entries must likewise be the suspend-capable one; otherwise the block cannot call suspending code. The scope and accessors are the same. ## Choosing between them Default to `andThen` with a constant: it is shorter and the value is visible at the point of definition, which makes the test readable. Escalate to `andThenAnswer` only when the entry genuinely depends on the call — its arguments, mutable test state, the real implementation, or a per-call exception. If several entries all need blocks, ask whether the collaborator wants a small hand-written double instead of a scripted sequence.

  • If the last entry of a chain is an andThenAnswer block, what happens on calls beyond the end of the sequence?
    The last entry repeats, and because it is a block it is re-executed on every further call. So a computed tail keeps producing fresh values from the current arguments and current test state, unlike a constant tail which freezes one value. That makes a computed final entry a neat way to say "special-case the first call, behave normally forever after".
  • Why prefer andThenAnswer { throw ... } over andThenThrows ex for a later failing step?
    andThenThrows stores the exception instance, so the same object is rethrown each time with a stack trace captured at the setup line, and any mutation on it — suppressed exceptions, for example — is shared across calls. Throwing from inside a block constructs a new exception per invocation, giving a call-site trace and letting the message include that call's arguments.

saying these in an interview costs you the question

  • Thinking andThenAnswer evaluates its block once when the stub is defined.
  • Believing the block form has a reduced scope without argument accessors or callOriginal().
  • Assuming a computed last entry is evaluated only once and then cached for later calls.
  • Using andThen with a value captured from mutable state and expecting it to reflect later mutations.
  • Putting assertions inside the block instead of capturing arguments and asserting afterwards.

context