MockK offers excludeRecords { } to keep nominated calls out of a mock's record. Mechanically, what does it change — including calls that already happened before it runs — and what capability do you give up for the excluded calls?
answer
- rewrites the record, not the answer table
- current = true purges already-recorded matches
- future matching calls never recorded
- excluded ⇒ invisible to ALL verify, wasNot Called passes
- coExcludeRecords for suspend
basics
~20 sexcludeRecords deletes matching calls from the mock's record and stops future matching calls being recorded. By default it also purges ones already recorded; pass current = false to keep those. Excluded calls become invisible to every verification, not just exhaustive ones.
solid answer
~50 s`excludeRecords { mock.noisyMember(any()) }` nominates a matcher pattern; MockK then treats matching invocations as if they never happened for verification purposes. Two effects: matching calls already in the record are removed — that is the default `current = true`, and `excludeRecords(current = false) { ... }` skips the purge — and any future matching invocation is simply not recorded. The answers still work: exclusion is about the record, not the answer table, so a stubbed excluded call still returns its stub. There is a `coExcludeRecords` variant for suspend members. The cost is total invisibility. An excluded call cannot be verified afterwards: a count assertion on it fails, and to `verify { mock wasNot Called }` the mock looks untouched. You are not softening an exhaustive check for that member; you are deleting the evidence. So use it for genuinely incidental traffic — accessors, telemetry — with a comment, and prefer splitting the interface when the noise is structural.
code
kotlin · 8 linesval order = mockk<Order>()
every { order.id } returns 42L
excludeRecords { order.id } // incidental: read inside log statements
service.process(order)
verify(exactly = 1) { gateway.charge(42L, any()) }
confirmVerified(order, gateway)go deeper
Say it removes nominated calls from what MockK counts, so strict checks stop complaining about incidental traffic, and note the calls still return their stubs.
Explain both effects — purging existing records by default and suppressing future ones — and that the block is a matcher block.
Lead with the trade-off: excluded calls are invisible to every verification, so wasNot Called would pass; then discuss when interface segregation or narrower strict scope is the better fix.
Treat repeated need for exclusion as a design signal about port shape, and set a team rule that exclusions are commented and never applied to members with cost or side effects.
## The problem it solves Exhaustive verification (`confirmVerified`, `verifyAll`, `verifySequence`) requires you to account for every recorded call on the mocks involved. Real collaborators are chatty: an id getter read inside a log statement, a `name` accessor used to build an error message, a metrics counter. None of those are the behaviour under test, but each is a recorded call, so a strict check demands a verification for it. `excludeRecords` lets you declare, once, that certain calls are not part of the interaction you are describing. ## What it actually does MockK keeps a per-mock **call record** — the ordered log of invocations that really happened — separate from the **answer table** written by `every { }`. `excludeRecords` operates only on the record: ```kotlin excludeRecords { order.id } // property getter excludeRecords { metrics.increment(any()) } ``` The block is a matcher block, exactly like a `verify` block: you may use `any()`, `eq()`, ranges, and so on, and the exclusion applies to every invocation matching that pattern. Two distinct effects: 1. **Retroactive purge (default).** The parameter is `current`, defaulting to `true`, and it means "also drop matching calls that are already in the record". Passing `excludeRecords(current = false) { ... }` limits the exclusion to future calls, leaving anything already recorded in place. 2. **Prospective suppression.** After the call, matching invocations are simply never added to the record. Because of (1), placement is flexible — you can exclude at the top of the test before exercising the system, or afterwards just before your strict assertion — but the two placements are not identical if you use `current = false`. Stubbing is untouched. An excluded call still consults the answer table and still returns whatever `every { }` said, still throws what it was told to throw, still counts as "handled" by a relaxed mock. Exclusion is a bookkeeping decision, not a behaviour change. For suspend members, `coExcludeRecords` is the matching entry point. ## The capability you give up This is the part interviewers probe. Exclusion is not scoped to exhaustive checks — it removes the call from the record that *all* verification reads. Consequences: - `verify { metrics.increment("saved") }` on an excluded call **fails**, because there is no record to match. - `verify(exactly = 3) { ... }` on excluded calls sees zero. - `verify { metrics wasNot Called }` **passes** even though the code hammered the mock. So excluding a member converts it into a blind spot for the whole test. If a regression later starts calling that member ten times per request, no assertion in that test will notice. That is an acceptable trade for a getter used in logging; it is a bad trade for anything with cost or side effects. ## When to reach for it, and what to reach for instead Good candidates: value-like accessors on entities passed to a mock, telemetry counters, `toString`-ish descriptive members — traffic that is definitionally not the contract. Before excluding, consider two better fixes: - **Narrow the collaborator.** If a port carries both domain operations and telemetry, split the interface. The telemetry then lands on a different mock which the strict block never names, so exhaustiveness never sees it — and you keep the ability to assert on the telemetry when you want to. - **Scope the strict block.** `verifyAll`/`verifySequence` are exhaustive only over the mocks named inside them, and `confirmVerified` takes only the mocks you pass. Often the noise comes from a mock that never needed to be in the strict set at all. When exclusion is genuinely right, write it near the top of the test with a one-line comment saying why the calls are incidental. A silent `excludeRecords` is a trap for the next reader, who will wonder why their new verification fails. ## Interaction with lifecycle Exclusions are registered on the mock, so if a mock is shared across tests, the exclusion set travels with it. MockK's clearing helpers can reset a mock's recorded calls between tests; combining shared mocks, exclusions and exhaustive verification is a recipe for tests that behave differently depending on execution order. Fresh mocks per test keeps all three honest. ## Summary line `excludeRecords` rewrites the record: retroactively by default, prospectively always, matcher-driven, answer-preserving — and the excluded calls disappear from every verification, which is why it is a scalpel and not a mute button.
- You exclude a call and then a colleague adds verify(exactly = 2) for it in the same test. What happens and why?The verification fails, reporting zero matching calls. Exclusion removes the invocations from the mock's call record entirely, and every verification function reads that record — there is nothing special-casing exhaustive checks. The fix is to stop excluding that member, or, better, to move it onto a separate collaborator so it can be both verified when wanted and ignored by the strict block.
- What is the difference between excludeRecords(current = true) and excludeRecords(current = false)?Both stop future matching invocations from being recorded. The difference is retroactive: with current = true, the default, MockK also drops matching calls already in the record, so it works even when placed after the code under test has run. With current = false only subsequent calls are suppressed, so anything already recorded stays and can still fail a strict check or satisfy a verification.
It is not a filter on the report — it is an eraser on the ledger. Once the line is gone, no later question about it can be answered.
saying these in an interview costs you the question
- Thinking exclusion only affects confirmVerified and that ordinary verify still sees the calls
- Assuming the excluded call stops returning its stubbed value
- Believing excludeRecords must be placed before the code under test runs — the default purges existing records
- Using it as the default cure for noisy strict blocks instead of narrowing the interface or the strict scope
- Excluding calls with real cost or side effects, creating a blind spot in the test