skip to content

Matcher Scoping Rules

Matchers only exist inside recording blocks, and mixing matchers with bare literals breaks the recorder. Interviewers ask this because diagnosing 'Missing matchers' errors is everyday MockK debugging.

on this pageshow

explore

questions

4

In MockK, why can matcher functions such as any() or match { } only be written inside an every, coEvery or verify block? Explain what actually happens at compile time and at runtime when one of them is called.

level: middleimportance: must knowfreq 45%

answer

  1. MockKMatcherScope = lambda receiver
  2. matcher registers + returns dummy signature value
  3. args equal to signature values mark matcher slots
  4. non-matcher arg = implicit eq()
  5. block may run several rounds — no side effects

basics

~20 s

Matchers are declared on MockK's MockKMatcherScope, the receiver of every/verify blocks, so elsewhere they do not compile. Inside, a call like any() registers a matcher with MockK's call recorder and returns a generated dummy value the recorder binds back to that argument position.

solid answer

~50 s

Two mechanisms. **Compile time:** `any()`, `eq()`, `match { }`, `capture()` and friends are declared on `MockKMatcherScope` — the lambda receiver of `every`/`coEvery` — and `MockKVerificationScope`, which extends it. Outside those lambdas the functions are simply not in scope, so it is a compile error, not a runtime surprise. **Runtime:** while the block runs, MockK has a call recorder active. Calling `any<Long>()` pushes a matcher onto that recorder and returns a generated *signature value* of the right type purely so the expression type-checks. When you then invoke the mocked method, MockK captures the invocation (mock, method, argument values) and reconciles it with the matchers registered since the block started: arguments equal to generated signature values become matcher slots, everything else is treated as `eq(literal)`. So a matcher is meaningful only as a direct argument expression of the recorded call — storing one in a variable and reusing it for two parameters registers one matcher for two slots and fails signature matching.

code

kotlin · 8 lines
kotlin
// val m = any<String>()            // does not compile: any() is on MockKMatcherScope

every { repo.findByName(any()) } returns user
verify { repo.findByName(match { it.startsWith("u") }) }

// reuse: an extension on the scope, called INSIDE the block
fun MockKMatcherScope.anyPositiveId() = match<Long> { it > 0 }
every { repo.findById(anyPositiveId()) } returns user

go deeper

for a junior

Know the practical rule: matchers live inside every/coEvery/verify and nowhere else, and that the compiler enforces it.

for a middle

Explain both halves — MockKMatcherScope as the lambda receiver, and the recorder that collects matchers and binds them to argument positions via generated signature values.

for a senior

Derive the failure modes from the model: one matcher for two slots, matchers created outside the recording window, side effects in a block MockK may re-run, and the two distinct exception texts you get.

for a principal

Frame it as a language-constrained design: Kotlin cannot pass a predicate in place of a typed parameter, so MockK trades a recording window for a type-safe, refactor-safe DSL — and then set team conventions (scope-receiver helpers, side-effect-free blocks) that live inside that constraint.

## The DSL is a scoped receiver, not free functions `every { }` and `coEvery { }` take a lambda whose receiver is `MockKMatcherScope`. `verify { }`, `coVerify { }`, `verifyOrder { }` and `verifySequence { }` take `MockKVerificationScope`, which extends `MockKMatcherScope` and adds verification-only helpers. Every matcher — `any()`, `eq()`, `neq()`, `isNull()`, `ofType()`, `match { }`, `more()`, `less()`, `range()`, `capture()` — is a member of that scope. That is the first, boring half of the answer: outside those lambdas the names are not resolvable, so `val m = any<String>()` at the top of a test does not compile. Candidates who say "matchers throw at runtime if used outside" have usually never tried it. ## What a matcher call does while the block runs MockK does not parse your lambda. It *executes* it with a call recorder installed, and observes what happens. A matcher call therefore has to do its work as a side effect: 1. it registers a matcher object with the active recorder; 2. it returns a **signature value** — a generated dummy instance/number of the declared type — so the surrounding expression type-checks and the mocked method can actually be invoked. That returned value is plumbing. It is not `null`, it is not your data, and your own code must never inspect it. ## Binding matchers to argument positions When the mocked call finally happens inside the block, MockK captures the invocation — the mock instance, the method, and the concrete argument values — and reconciles it against the matchers registered since the block started. Arguments whose values are the generated signature values mark the positions the matchers belong to, in registration order; any other argument is treated as an implicit `eq(literal)`. Because signature values must be distinguishable, MockK may execute the recording lambda more than once to resolve an ambiguous signature. Two failure modes fall straight out of this model: - **No mock call was recorded** at all inside the block: `io.mockk.MockKException: Missing calls inside every { ... } block.` - **A call was recorded but the matchers could not be bound**: `Failed matching mocking signature for ...`, with a `left matchers: [...]` line naming the matchers MockK could not place. ## Consequences you can state in an interview - A matcher must appear as a **direct argument expression** of the recorded call. `val id = any<Long>(); repo.link(id, id)` registers one matcher for two argument slots and fails signature matching. - A matcher cannot be created before the block and passed in — there is no recorder running yet, and the function is not even in scope. - The recording window is bounded by the lambda and is held per executing thread. Matchers created in code that runs *later* — for example inside an `answers { }` body at answer time, or on a thread you spawn inside the block — are not part of the recording. Inside an answer body you are in a different scope anyway and use argument accessors, not matchers. - Because the block may run more than once, it must be free of side effects: it is a description of a call, not test logic. - Reuse is done with **receiver-typed extension functions** — `fun MockKMatcherScope.anyPositiveId() = match<Long> { it > 0 }` — called from *inside* the block, where the recorder is live. That is the only sanctioned way to share matcher expressions. ## Why MockK is built this way Kotlin gives no syntax for "pass a predicate in place of this parameter": the call must be made with real values of the declared types. By generating throwaway values and watching the resulting invocation, MockK gets to reuse the mocked type's own API as the stubbing DSL — type-safe, refactor-safe, with IDE completion. The price is exactly the scoping rule: matchers are only meaningful during the recording window, and describing a call is not the same as making one.

  • Can you factor MockK matchers into a shared helper used by several tests?
    Yes, but the helper must run inside the recording block. Declare it as an extension on MockKMatcherScope — `fun MockKMatcherScope.anyActiveUser() = match<User> { it.active }` — and call it as an argument of the mocked call. What you cannot do is call a helper before the block and pass its result in: there is no recorder active then, and the matcher functions are not in scope. The cleanest reuse unit is usually a helper that wraps the whole `every { ... } returns ...` statement.
  • What value does any() actually return at runtime?
    A generated signature value of the declared type — a throwaway number or instance produced purely so the expression type-checks and the mocked method can be invoked. MockK later recognises that value among the recorded arguments to work out which position the matcher belongs to. It is not null and it carries no meaning for your code, which is why using it in your own logic inside the block is nonsense.

Recording an every block is like miming a phone call so someone can note the number: you have to dial something, so MockK hands you fake digits and remembers which ones were fake. Outside the mime, the fake digits mean nothing.

saying these in an interview costs you the question

  • Saying any() returns null, or that MockK passes the matcher object itself as the argument
  • Claiming matchers can be created outside the block and stored in a val or a @BeforeEach field
  • Believing MockK parses the lambda's source instead of executing it under a recorder
  • Reusing one matcher's returned value for several parameters and expecting several matchers
  • Putting side-effecting setup inside every/verify, unaware the block can be evaluated more than once

context

open as a page

In MockK, what happens when one argument of a stubbed call is a matcher such as any() and another is a plain literal like 42? When does that combination break, and why does wrapping the literal in eq() fix it?

level: middleimportance: should knowfreq 40%

basics

~20 s

MockK treats a non-matcher argument as an implicit eq(literal), so mixing usually works. It breaks when MockK cannot tell a generated signature value apart from your literal — likeliest for tiny value spaces such as Boolean. Wrapping every argument in eq() makes matchers and arguments line up exactly.

open as a page

A Kotlin test fails with `io.mockk.MockKException: Missing calls inside every { ... } block.` What does MockK mean by that, and how would you diagnose it systematically?

level: seniorimportance: should knowfreq 35%

basics

~20 s

It means MockK's recorder saw no mockable call inside the block. Usually the target is not a mock, the call was resolved statically (top-level or extension function, an unmocked static), it hit a real object returned by the mock, or the block only computed values without invoking the mock.

open as a page

MockK evaluates the lambda passed to every or verify under a call recorder, and may execute it more than once while it resolves the call signature. Given that, how would you factor shared stubbing and verification helpers across a large Kotlin test suite?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Treat recording blocks as pure descriptions: one mock call, no side effects, nothing built inside them. Share matchers as MockKMatcherScope extension functions called inside the block, and share whole stubs as ordinary functions that wrap the entire every/returns statement.

open as a page