skip to content

MockK's confirmVerified(mock) throws when a mock has recorded calls that nothing verified. Mechanically, which invocations end up in that record, what marks one as verified, and where in a test must the call be placed?

level: middleimportance: should knowfreq 45%

answer

  1. two books: answer table vs call record
  2. every{}/verify{} blocks record matchers, not calls
  3. matchers decide coverage — any() covers all
  4. exactly = 0 marks nothing
  5. confirmVerified goes last; it verifies nothing

basics

~20 s

Every real invocation on the mock is recorded — stubbed, relaxed-default, property access, suspend calls alike. Any verify block whose matchers match a recorded call marks it verified. confirmVerified verifies nothing itself; it asserts the leftover set is empty, so it goes last.

solid answer

~50 s

A MockK mock keeps two separate books: the **answer table** written by `every { }`, and the **call record** of invocations that actually happened. `confirmVerified` reads only the second one. Everything the code under test invokes on the mock is recorded: stubbed calls, calls answered by a relaxed default, property getters and setters, suspend calls. What you write *inside* `every { }` or `verify { }` is not an invocation — those blocks run in recording mode to build matchers, so they never add records. A record becomes "verified" when any `verify` / `coVerify` block, in any ordering mode, contains an entry whose matchers match it. Broad matchers cover a lot: `verify { repo.save(any()) }` marks every recorded `save`. `verify(exactly = 0) { ... }` matches nothing, so it marks nothing. `confirmVerified(mock)` asserts the unverified set is empty and does no verification of its own — placing it before your `verify` calls makes it fail on everything. It belongs at the end.

code

kotlin · 9 lines
kotlin
val repo = mockk<OrderRepo>()
every { repo.findById(1) } returns order   // answer table, not a record
every { repo.save(any()) } returns Unit

service.touch(1)                            // findById(1), save(x), save(y)

verify(exactly = 1) { repo.findById(1) }
verify { repo.save(any()) }                 // marks BOTH saves verified
confirmVerified(repo)                       // passes

go deeper

for a junior

Say that confirmVerified checks nothing was left unverified, and that it must come after the verify calls because it does not verify anything itself.

for a middle

Distinguish the answer table from the call record, list what gets recorded (including relaxed defaults and property access), and note that matchers decide coverage.

for a senior

Add the exactly = 0 nuance, the redundancy with verifyAll/verifySequence, and the shared-mock hazard where records accumulate across tests.

for a principal

Frame it as a design signal: a mock whose record keeps filling with calls nobody intends to verify is usually an interface that does too much, not a verification problem.

## Two books per mock A MockK mock maintains two independent structures: 1. **The answer table** — built by `every { ... } returns ...`. It maps a matcher signature to the behaviour the mock should exhibit. 2. **The call record** — an ordered log of invocations that actually occurred: which member, which arguments, in what order. Stubbing writes only to the first. Verification reads only the second. Keeping these apart explains most confusing MockK behaviour, including everything about exhaustive verification. (`checkUnnecessaryStub` is the mirror-image tool that inspects the *answer table* for entries nothing ever hit.) ## What lands in the call record Every invocation the code under test makes on the mock: - Calls with a matching stub, answered from the answer table. - Calls with no stub on a relaxed mock, answered by the default value. - Property reads and writes — a `val`/`var` access compiles to a getter/setter call and is recorded like any other member. - Suspend calls, recorded the same way (verified with the `co` variants). What does **not** land there: the calls you write inside `every { }`, `verify { }`, `coEvery { }` and friends. Those blocks execute in a special recording mode where invocations build matcher descriptions rather than counting as usage. This is why writing an `every` block never satisfies a later verification, and why a stub you set up but never exercised leaves the call record empty. ## What marks a record verified Any verification block — `verify`, `coVerify`, `verifyOrder`, `verifyAll`, `verifySequence` — marks the recorded calls its entries matched. Two consequences matter: - **Matchers decide coverage, not intent.** `verify { repo.save(any()) }` marks *all* recorded `save` calls verified, no matter how many there were or what they carried. If you want a count, that is a separate concern (`verify(exactly = n)`), but note the count assertion also does the marking. - **Assertions that match nothing mark nothing.** `verify(exactly = 0) { repo.delete(any()) }` asserts an absence; there is no record for it to mark, so it contributes nothing towards satisfying `confirmVerified`. Because `verifyAll` and `verifySequence` are already exhaustive over the mocks they name, calling `confirmVerified` after one of them is a no-op — the exhaustive block would have failed first. `confirmVerified` earns its place with the *tolerant* modes: several focused `verify` calls that each state one thing, followed by one statement that closes the door. ## Placement and failure `confirmVerified` performs no verification itself. It is an assertion about state accumulated so far, so it must come **after** every `verify` in the test: ```kotlin verify(exactly = 1) { repo.findById(1) } verify(exactly = 1) { repo.save(any()) } confirmVerified(repo) ``` Put it first and it fails on the very calls you were about to verify. It accepts several mocks (`confirmVerified(repo, gateway)`) and checks each independently. On failure it throws an `AssertionError` naming the calls that were recorded but never matched — read that list, because it is telling you exactly what your production code did that your test never described. ## Where the record starts and stops The record is per-mock and lives as long as the mock does. If a mock is created fresh per test — the usual arrangement, whether by `mockk<T>()` in the test body or by MockK's annotation-driven setup — the record starts empty each time. If a mock is shared across tests, calls accumulate, and `confirmVerified` in the second test will see the first test's traffic unless the record was reset in between (MockK's clearing helpers can reset recorded calls specifically). Shared mocks plus exhaustive verification is a combination worth avoiding for exactly this reason. ## The relaxed-mock interaction Relaxed mocks and `confirmVerified` pull against each other. Relaxation exists so incidental calls just work without stubbing — but every one of those incidental calls is still recorded, and `confirmVerified` will demand you account for it. On a narrow port this is a useful discipline: it surfaces calls nobody intended. On a wide interface it becomes a chore, and either the interface should be narrowed or the incidental members should be nominated for exclusion from the record. ## The one-line summary for an interview `confirmVerified` does not verify; it asserts that the call record has no leftovers. Records come from real invocations only, coverage is decided by your matchers, and the call belongs at the end of the test.

  • Does calling confirmVerified after a verifyAll block add anything?
    No. verifyAll is already exhaustive over the mocks it names — it fails if any recorded call on them is left uncovered — so by the time confirmVerified runs there can be nothing left. confirmVerified is for the tolerant style: several focused verify calls, each stating one fact, then one statement asserting nothing else happened.
  • Your test stubs a method with every but never verifies it, and confirmVerified passes. Is that expected?
    Yes, if the stubbed method was never actually invoked. Stubbing writes to the answer table, not the call record, so an unused stub leaves nothing for confirmVerified to complain about. That blind spot is precisely what checkUnnecessaryStub covers — it inspects the answer table for stubs no call ever matched.

saying these in an interview costs you the question

  • Believing confirmVerified performs verification, and putting it before the verify calls
  • Thinking a call written inside an every block counts as a recorded invocation
  • Assuming verify with any() only covers one call, so confirmVerified will still flag the rest
  • Expecting verify(exactly = 0) to satisfy confirmVerified for that member
  • Forgetting that relaxed mocks record their auto-answered calls too

context