skip to content

Stubbing

Everything the every { } block can do: fixed returns, chained sequences, computed answers, and stubbing properties or whole call chains. Interviewers push past 'returns' to see if you can shape realistic collaborator behavior.

on this pageshow

explore

questions

14

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

open as a page

In MockK, how do you stub and verify a Kotlin property on a mock — both a read-only `val` and an assignable `var` — and what is MockK actually intercepting when you do?

level: middleimportance: must knowfreq 55%

basics

~20 s

MockK intercepts accessor calls, not fields. Stub reads with every { mock.prop } returns x. An assignment is a setter call needing an answer, so use justRun { mock.prop = any() }, and assert with verify { mock.prop = 5 }.

open as a page

In MockK, how do you make one stubbed function return a different value on each successive call, and what does that stub return once the list of values is used up?

level: middleimportance: must knowfreq 55%

basics

~20 s

Chain answers: every { c.next() } returns 1 andThen 2 andThen 3, or returnsMany listOf(1, 2, 3). MockK hands out one entry per matching call, in order. When the list runs out it repeats the last entry forever — it does not throw and does not restart.

open as a page

In a MockK test you write `every { repo.find(any()) } returns A` and later, in the same test, `every { repo.find(1) } returns B`. Which stub wins, and does re-stubbing reset an existing answer chain or the mock's recorded calls?

level: seniorimportance: must knowfreq 40%

basics

~20 s

The most recently registered matching stub wins, so find(1) returns B and everything else returns A. Declaration order decides, not how specific the matcher is. Re-stubbing a call installs a new answer, so any chain in the old one is abandoned and sequencing starts over — but recorded calls are untouched.

open as a page

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%

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.

open as a page

You mocked a collaborator with MockK and the code under test calls one of its Unit-returning methods. Why does that call still fail on a non-relaxed mock, and what exactly does `just Runs` do about it?

level: middleimportance: should knowfreq 45%

basics

~20 s

A plain mock has no answer for any call, including Unit ones, so it throws a MockKException. every { x.log(any()) } just Runs registers the answer "return Unit and do nothing". justRun { x.log(any()) } is the same thing in one call.

open as a page

What does MockK's `callOriginal()` do inside an answer block, on which kinds of mocks does it work, and why would you use it instead of simply leaving the function unstubbed?

level: seniorimportance: should knowfreq 35%

basics

~20 s

callOriginal() invokes the real implementation behind the intercepted call and returns its result. It needs an original to call — a spy, or a real object/class/static/constructor that MockK has patched — and fails on a plain interface mock. Use it to keep real behavior while still recording, wrapping or overriding the call conditionally.

open as a page

What does MockK do internally when a test writes a stub over a chain of calls, such as `every { repo.session(any()).user.name } returns "ann"`, and what constraints does that place on the intermediate types?

level: seniorimportance: should knowfreq 40%

basics

~20 s

MockK walks the chain during recording and auto-creates a child mock for each intermediate return, registering it as that call's answer. So every level becomes stubbed and verifiable, and each intermediate type must itself be mockable.

open as a page

Using MockK, how would you stub an HTTP client so that the first two calls throw an IOException and the third returns a response — and what should you be careful about when reusing one exception instance across calls?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Mix terminators in one chain: every { client.get(any()) } throws IOException("boom") andThenThrows IOException("boom") andThen response. Each entry is a separate answer. The same Throwable instance is rethrown every time it is used, so its stack trace stays from where you built it and any mutation on it is shared.

open as a page

A team's MockK stubs have grown into `answers { }` blocks with branching, mutable state and assertions inside them. How do you judge when answer-block logic has gone too far, and what do you do about it?

level: principalimportance: should knowfreq 22%

basics

~20 s

Answer blocks should compute a return value from the call, nothing more. Branching on the invocation, accumulating state, or asserting inside the block means the stub has become an untested implementation living in a lambda. Replace it with a small hand-written double, and move assertions to captures outside.

open as a page

A test needs three levels of chained MockK stubbing to set up one collaborator. How do you decide between keeping the deep stub and changing the design or the test?

level: principalimportance: should knowfreq 28%

basics

~20 s

Ask who owns the chain. If you own the types, a three-level stub is a design signal — pass the leaf value in, or use a real object or fake. If the chain belongs to a third-party API you cannot reshape, deep stubs are legitimate; isolate them behind a test fixture.

open as a page

A test needs a collaborator that fails on the first two attempts and succeeds afterwards. Compare encoding that as a MockK `andThen` chain against re-stubbing the same call between phases of the test — how do you choose, and what breaks later?

level: principalimportance: should knowfreq 25%

basics

~20 s

A chain ties behavior to call position, so it is compact but breaks whenever the number of calls changes. Re-stubbing ties behavior to a point in the test's narrative, so it survives extra calls but only works if the test can pause between phases. Choose by whether the test's story is about counting calls or about a state change.

open as a page

Beyond the arguments, what invocation metadata does MockK expose inside an `answers { }` block, and when is reaching for `call`, `self` or `nArgs` actually justified?

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

The answer scope exposes the whole invocation: call, call.invocation (with self, method and args), plus shortcuts args and nArgs. Justified uses are answers shared across overloads or several mocks, and diagnostics. If a stub needs the invoking object's identity to decide, that is usually a smell.

open as a page

MockK exposes an infix DSL written as `mock getProperty "speed"` and `mock setProperty "speed" value 33`. What problem does it solve, what does it require, and what does it cost you?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

It reaches properties the test cannot name in code — private ones — by string, reflectively. On a spy you need spyk(obj, recordPrivateCalls = true) for private access to be recorded. Cost: no compile-time safety, rename-unsafe, untyped results.

open as a page