skip to content

You stubbed one call with `every` on a mock created with MockK's `relaxed = true`, and the code under test calls the same method with arguments your stub does not match. What happens, and why is that harder to diagnose than on a strict mock?

level: seniorimportance: should knowfreq 33%

answer

  1. stub = (mock, method, matchers); miss → no stub applies
  2. strict: "no answer found for: …" names the call
  3. relaxed: silent default — 0 / "" / child mock
  4. causes: changed args, identity-based equals, overloads
  5. diagnose: flip to strict, or verify to see recorded calls

basics

~20 s

Nothing fails: the call misses your stub and falls through to the relaxed default — 0, empty string, or a child mock. A strict mock would have thrown no answer found for: naming the exact call. Relaxed mode converts a precise, immediate error into a silent wrong value.

solid answer

~50 s

An `every` stub is registered against a **call signature including its argument matchers**. If the production call's arguments do not match, the stub simply does not apply. On a strict mock that is a `MockKException` whose message starts `no answer found for:` and prints the actual call — the single most useful diagnostic MockK produces. On a relaxed mock, the default generator answers instead, and the test carries on with a meaningless value. What then happens is one of three things: the test fails much later with a confusing message about a mock or a zero; the test passes for the wrong reason because the default coincides with the expected outcome; or a child mock escapes into saved state and surfaces elsewhere. The standing technique: when a relaxed-mock test behaves oddly, **temporarily create the mock strict**. The resulting exception names the unmatched call directly. Then decide whether to fix the matcher or make the stub deliberately broader with `any()`.

code

kotlin · 7 lines
kotlin
val repo = mockk<Repository>(relaxed = true)
every { repo.find(1) } returns Account(1)

val result = repo.find(2)   // no error: relaxed default (a child mock of Account)

// with `val repo = mockk<Repository>()` the same call throws:
// io.mockk.MockKException: no answer found for: Repository(#1).find(2)

go deeper

for a junior

Know that stubs match on arguments, and that on a relaxed mock a non-matching call quietly returns a default instead of failing.

for a middle

Explain the precedence — matching stub first, relaxed default as fallback — and name the strict-mock error message you would otherwise have seen.

for a senior

Own the diagnosis: flip to strict or verify to inspect recorded arguments, and know the realistic causes such as changed production arguments and identity-based equality.

for a principal

Set the boundary at suite level — relaxation absorbs calls nobody asserts on, explicit stubs cover values under assertion — and pair it with unused-stub checking so never-matching stubs do not accumulate.

## The mechanic MockK stores a stub as *(mock, method, argument matchers) → answer*. When a call arrives, MockK looks for a registered stub whose matchers accept the actual arguments. `every { repo.find(1) } returns account` registers an `eq(1)` matcher; a call of `repo.find(2)` does not match it. What happens on a miss depends entirely on how the mock was created: - **strict** (`mockk<Repository>()`): `MockKException`, message beginning `no answer found for:` followed by the rendered call, e.g. `Repository(#1).find(2)`. Test fails immediately, at the exact call, naming the arguments; - **relaxed** (`mockk<Repository>(relaxed = true)`): the default generator answers — `0`, `""`, `Unit`, an empty array, or a fresh relaxed child mock for other types. Relaxation is a **fallback for unmatched calls**, not a replacement for stubs. Explicit stubs always win when they match; the default only fills the gaps. That is exactly why a *near-miss* is invisible. ## Why the miss happens in practice Rarely because someone typed the wrong literal. The realistic causes: - **Production code changed its arguments.** A parameter is added, an id becomes a value class, a timestamp is now passed. The stub still compiles and still registers — it just never matches again. - **Equality is not what you assumed.** MockK's `eq` uses structural equality (`equals`). A class without a meaningful `equals` — many builders, some framework DTOs, arrays — compares by identity, so a structurally-identical argument still misses. - **Defaults and overloads.** A default parameter filled in by the caller, or a different overload being resolved, changes the signature that actually arrives. - **Wrapping.** A collaborator wraps the value (`Optional`, a request object) before the call reaches the mock. In every case, strict mode names it and relaxed mode swallows it. ## The three downstream outcomes 1. **Late confusing failure.** Your assertion compares a domain object to a child mock: `expected: Account(id=1) but was: Account(#7)`. Nothing in that message says "your stub did not match". 2. **Wrong-reason pass.** The default coincides with the expected value — a `count()` that would have been `0` anyway, an empty list that satisfies "nothing to do". The test is now green and worthless, and it will stay green after the behaviour it claims to check is deleted. 3. **Escaped mock.** The child mock is stored in a collection, cache, or entity and surfaces in a later assertion or, in longer suites, a later test. ## Diagnosis The reliable procedure, in order: 1. **Flip the mock to strict** (`mockk<T>()`), rerun. The `no answer found for:` message prints the actual call and its actual arguments; comparing that with your `every` line usually ends the investigation in seconds. 2. If it must stay relaxed, **verify the call you believe happened** — `verify { repo.find(1) }`. Verification failure output lists the calls MockK actually recorded on that mock, which shows the real arguments. 3. Fix the mismatch by correcting the matcher, or widen it deliberately: `every { repo.find(any()) } returns account` when the argument genuinely does not matter to this test. ## The judgement, kept narrow This is not an argument about whether relaxed mocks are good. It is about *where the value being defaulted matters*. A relaxed default for a fire-and-forget metric call costs nothing. A relaxed default for a value your assertion depends on removes MockK's best diagnostic and replaces it with a plausible lie. So: even on a relaxed mock, **stub explicitly every call whose return value the test reasons about** — relaxation should only be absorbing the calls nobody is asserting on. A related failure worth naming: a stub that never matches is also a stub that never gets used, and a suite full of never-matching stubs is silently rotting. MockK offers unused-stub checking as a separate facility for that; the point here is that relaxation removes the *runtime* signal, so something else has to supply it. ## Interview framing Say: *the call falls through to the relaxed default because stubs match on arguments; strict mode would have thrown `no answer found for:` with the actual call printed. The realistic causes are changed production arguments and surprising equality. Diagnose by flipping to strict or by verifying the call to see the recorded arguments, and keep explicit stubs for any value the test asserts on.*

  • Your stub uses a data-class argument and still never matches. What is the likely cause?
    MockK's `eq` matcher compares with `equals`, so the argument type must implement structural equality for a structurally-identical instance to match. Data classes do, but arrays, many builders and some framework DTOs fall back to identity equality, so a freshly built but equal-looking argument misses the stub. Options are to match on a property with `match { }`, use `any()` when the argument is irrelevant to the test, or fix the type's equality if you own it.
  • How do you get the benefit of relaxed mocks without losing the unstubbed-call signal?
    Scope the relaxation to the calls nobody asserts on and keep explicit stubs for every value the test reasons about, so a miss on an important call still produces a wrong result you notice — or, better, keep the mock strict and stub the noise deliberately. When a relaxed test misbehaves, flipping the mock to strict for one run restores the precise `no answer found for:` diagnostic without changing anything else.

A strict mock is a form that rejects an unexpected field and tells you which one. A relaxed mock is a form that silently writes a zero into any field it does not recognise — you only discover the problem when the total at the bottom is wrong.

saying these in an interview costs you the question

  • "The relaxed default overrides the stub, so my every was ignored" — matching stubs always win; the default only fills unmatched calls.
  • "If the stub didn't match, MockK warns you" — on a relaxed mock the miss is silent at runtime.
  • "Relaxed mocks make tests less brittle, so a near-miss is fine" — it converts a precise failure into a plausible wrong value.
  • "eq() compares by reference" — it uses structural equality, which is exactly why identity-based `equals` types surprise people.
  • Diagnosing by adding more relaxation instead of temporarily removing it.

context