A MockK test hands the same MutableList to capture(...) in both an every block and a later verify block. It ends up with twice as many captured arguments as there were real calls. Explain when MockK writes into a capture target, and how you would structure the test instead.
answer
- matching writes, not calling
- every → at call time
- verify → replays recorded calls, writes again
- same list in both = 2x entries
- one capture site per assertion
basics
~20 sCapture is a side effect of matching, so it fires wherever matching happens: inside every it fires when production calls the mock, and inside verify it fires again as MockK re-matches the recorded calls. The same list in both gets each argument twice. Capture in one place only.
solid answer
~50 s`capture(...)` is a matcher that also writes the actual argument into your slot or list. It fires every time that matcher is evaluated against a real invocation — not once per test. In an `every` block, the matcher is evaluated at call time, so values land while the code under test runs. In a `verify` block, MockK walks the mock's recorded calls and re-matches them, so the same arguments are written again at verification time. One list shared by both therefore receives each argument twice; running the same `verify` twice, or verifying the same mock in two blocks that both capture, doubles again. The fix is structural: pick one capture site. I capture in `verify` by default — the values are then read after the interaction has happened, the block already states the expected count, and a failing count fails before the content assertion. Capturing in `every` is for when you need the argument *during* the call, e.g. to build the stubbed answer from it.
code
kotlin · 12 lines// BAD: same list captured at two sites
val args = mutableListOf<Order>()
every { gateway.send(capture(args)) } returns Ack
service.submit(order) // args = [order]
verify { gateway.send(capture(args)) } // args = [order, order]
// GOOD: stub without capturing, capture once during verification
val sent = mutableListOf<Order>()
every { gateway.send(any()) } returns Ack
service.submit(order)
verify(exactly = 1) { gateway.send(capture(sent)) }
assertEquals(listOf(order), sent)go deeper
Know that the list grew because the same capture ran in two places, and that you should capture in only one block.
Explain the two evaluation sites — every fires at call time, verify fires while replaying recorded calls — and pick verify as the default.
Diagnose from the symptom (a clean multiple of the expected size), rule out class-level state and repeated verifies, and articulate when capturing during every is justified.
Set the convention: capture at one site, assert count with the verification mode and content with the captured target, and treat capture-in-every as a signal that the stub should use an answers block instead.
## Capture is not a test-lifecycle event The mental model that produces this bug is "the slot gets filled once, when the call happens". The accurate model is: `capture(target)` is an argument matcher, and *matching* is what writes the value. So the question is never "when did the call happen" but "how many times was this matcher evaluated against a real invocation". ## Two evaluation sites **In `every { }`.** The matchers you record become the stub's signature. When production code calls the mock, MockK matches the invocation against recorded stubs; when your capture matcher takes part in a successful match, the argument is written. Values therefore appear while the code under test is running, before you assert anything. **In `verify { }`.** Verification does not re-run production code. MockK keeps a record of the invocations the mock actually received, and the verify block re-matches those recorded calls against the matchers you just wrote. Your capture matcher is evaluated again — against history — and writes again. Hand the same `MutableList` to both sites and each argument is appended twice: once at call time, once at verification time. A `CapturingSlot` hides this (the second write just overwrites with the same value), which is exactly why the symptom usually surfaces as a mysteriously doubled list rather than a wrong slot. ## Other multipliers - Verifying the same mock twice with capture in both blocks (say a loose `verify` followed by `verifyOrder`) captures twice. - A stub redefined in a helper and re-registered adds another matching site. - Capturing in `every` and then asserting `list.size` as the call count conflates "times matched" with "times called" — they are only equal when there is exactly one capture site. ## Choosing a capture site **Default to `verify`.** The verification block already states how many calls you expect, so the count assertion and the content assertion sit next to each other; the values are read after the interaction, which is when you want them; and there is exactly one evaluation site, so the list length is honest. **Capture in `every` when the argument shapes the answer.** If the stub must respond based on what it was given, the argument has to be available during the call. In that case, prefer reading the argument inside an `answers { }` block over capturing into an outer list, so you keep a single source of truth about what the mock did; if you do capture in `every`, do not also capture into the same target in `verify`. **Never share one target across sites.** If you genuinely need both, use two different lists with names that say which site they belong to. It reads badly, which is a useful signal. ## Diagnosing it in a real suite The fingerprint is a size assertion that is a clean multiple of the truth — 2x with one capturing verify, 3x with two. Check three things in order: is the same slot/list named in more than one block; is the list a class property surviving from a previous test; is the same verify running more than once (a loop, a parameterised test, a shared helper). All three produce inflated captures, and only the first is about capture timing. ## Why MockK behaves this way Re-matching recorded calls during verification is what lets `verify` use the full matcher DSL — the same `any()`, `match {}` and `capture()` expressions work in both blocks precisely because verification replays history through the same matcher machinery. The doubled capture is the price of that uniformity, not a bug: a matcher with a side effect will run its side effect wherever the matcher runs.
- If capture in verify replays recorded calls, why does a slot not show the same doubling as a list?A CapturingSlot holds a single value and each write overwrites it, so the second write at verification time stores the same argument again and the symptom is invisible. A MutableList appends, so every extra evaluation shows up as an extra element. That is why a doubled list size is often the first sign of two capture sites.
- When is capturing inside an every block the right choice?When the stub's behaviour depends on the argument — you need the value during the call, not afterwards. In that case an `answers { }` block that reads the argument directly is usually cleaner than an outer capture, and if you do capture in `every` you must not capture into the same target again during verification.
saying these in an interview costs you the question
- Assuming a capture target is written exactly once per real call regardless of where capture() appears
- Thinking verify re-executes production code rather than re-matching recorded calls
- Using the captured list's size as the authoritative call count while capturing in two places
- Blaming the doubling on the mock not being cleared between tests when the real cause is two capture sites
- Claiming capture() is illegal inside every blocks