skip to content

In MockK, if the same interaction is verified twice in one test — say `verify(exactly = 1) { repo.save(user) }` runs inside two different helper functions — does the second verification fail because the call was already 'used up'?

level: middleimportance: nice to knowfreq 25%

answer

  1. verify reads, never consumes
  2. count = matches in the full recorded list
  3. two exactly = 1 blocks ≠ expecting two calls
  4. overlapping broad/narrow counts both valid
  5. list resets only on new mock / cleared calls

basics

~20 s

No. MockK verification is non-destructive: each verify block re-scans the mock's recorded calls and counts matches from scratch. Counts are absolute, not remaining, so both assertions see one call and both pass — and counts never add up across blocks.

solid answer

~50 s

Verification does not consume recorded calls. Each `verify` block counts, from the complete recorded-call list of that mock, how many entries match the described call. Nothing is marked as spent, so: - verifying the same interaction twice passes twice; - two blocks of `verify(exactly = 1) { repo.save(user) }` do **not** together assert two calls — they each assert one, and a genuine second call would fail both; - overlapping assertions can coexist: `verify(exactly = 3) { repo.save(any()) }` and `verify(exactly = 1) { repo.save(admin) }` describe the same run at different granularity. Practical consequences: verification helpers are safe to compose and call repeatedly, and a helper invoked twice does not mean "expect two calls" — express that with `exactly = 2`. The recorded list is reset when the mock is recreated or its recorded calls are cleared, and MockK's exhaustive-verification facilities are the separate feature that cares about which calls were covered.

code

kotlin · 8 lines
kotlin
service.save(user)   // exactly one real call

verify(exactly = 1) { repo.save(user) }
verify(exactly = 1) { repo.save(user) }   // still passes: nothing was consumed

// broad and narrow assertions over the same run
verify(exactly = 3) { repo.save(any()) }
verify(exactly = 1) { repo.save(adminUser) }

go deeper

for a junior

Know the outcome: verifying the same call twice is harmless, and repeating a verify does not add up to a higher expected count.

for a middle

Explain the mechanism — each block recounts matches over the mock's full recorded-call list and mutates nothing — and derive the loop/helper trap from it.

for a senior

Use it deliberately: compose verification helpers freely, parameterise counts instead of repeating fixed ones, and note that counting never implies exhaustiveness.

for a principal

Set the expectation for the suite: count assertions describe specific interactions, so decide separately and sparingly where an 'and nothing else' claim is worth its brittleness.

## The model: a recorded-call list, read many times Every MockK mock keeps a list of the calls it actually received. A `verify` block does not mutate that list — it reads it. Given the method and argument matchers you described, MockK counts how many entries match, and compares that number to the bounds you asked for (`exactly`, or `atLeast`/`atMost`). Then it moves on, leaving the list exactly as it found it. That single fact answers the question and several of its cousins: - **Repeated verification is idempotent.** Two identical `verify(exactly = 1)` blocks both see one matching call; both pass. - **Counts never accumulate.** Writing the same `exactly = 1` block twice is not a way to assert two calls. If the code really made two calls, both blocks fail, because each one computes 2 and expects 1. - **Overlapping assertions are legal and independent.** `verify(exactly = 3) { repo.save(any()) }` together with `verify(exactly = 1) { repo.save(adminUser) }` is a coherent description of one run at two granularities. Nothing is subtracted from the broad count by the narrow one. - **Order of verify blocks is irrelevant** to counting. Blocks that assert ordering are a different feature; a plain count block does not care where in the test it sits, only what the recorded list contains at that moment. ## Why this design is convenient Because counts are absolute rather than remaining, verification composes. You can build helper functions — `repo.verifySavedOnce(user)` — and call them from several places, layer a shared "nothing unexpected happened" helper on top of test-specific assertions, and re-verify inside a loop over test data without bookkeeping. No API in MockK requires you to track which recorded calls a previous assertion has already claimed. The cost is that a green suite of count assertions does not, by itself, mean the mock received *only* those calls. Two assertions can both pass while the mock also received a third, entirely unasserted interaction. MockK has a separate exhaustive-verification facility for the "and nothing else" claim; counting alone never makes it. ## The trap this creates A verification helper that hides `verify(exactly = 1) { ... }` reads, at the call site, like "this happened once". Call it twice — in a loop over cases, or from two composed helpers — and a reader may believe the test now expects two calls. It does not; it expects one, twice over. If a helper is genuinely reused per iteration, the count must be parameterised (`verifySaved(times = 2)`) or the assertion moved outside the loop with the right number in it. This is the practical reason to state counts in the helper's name or signature rather than burying a fixed `exactly = 1` inside it. ## What does change the recorded list The list grows only through real invocations by the code under test. Calls written inside `every { }` blocks are recording, not invocation, and never appear in it. It shrinks only when you reset it — by creating the mock fresh for each test, or by clearing recorded calls between tests. If a count assertion is order-dependent across a test class, that reset is what is missing; verification itself never removed anything. ## How to answer crisply "No — MockK verification is non-destructive. Each block recounts matches over the mock's full recorded-call list, so repeated or overlapping verifications are independent, counts do not add up across blocks, and helpers are safe to compose. The corollary is that passing count assertions never imply the mock received nothing else."

  • If counts do not accumulate, how do you assert that a helper's interaction happened once per loop iteration?
    Move the count out of the loop, or parameterise it. Either verify once after the loop with exactly = n, where n is the number of iterations, or give the helper a times parameter it passes to exactly. Calling a fixed exactly = 1 helper n times asserts 'one call' n times over and will fail outright as soon as the loop makes more than one call.
  • If verification never consumes calls, how would you assert that a mock received nothing beyond what you verified?
    Counting alone cannot express that — two passing assertions say nothing about a third unasserted call. MockK provides a separate exhaustive-verification facility for the 'and nothing else' claim, which is a different tool with its own tradeoffs around test brittleness. Reach for it deliberately, on the few collaborators where an unexpected interaction would be a real defect.

saying these in an interview costs you the question

  • Believing verification consumes recorded calls, so a second identical verify would fail
  • Writing the same exactly = 1 block twice to mean "expect two calls"
  • Assuming a set of passing count assertions proves the mock received nothing else
  • Thinking calls made inside every blocks appear in the recorded list
  • Explaining order-dependent counts as verification side effects rather than a mock reused without resetting

context