A previously green test that uses MockK's verifySequence starts failing after a teammate adds a metrics call to the same collaborator — the assertions themselves were not touched. Explain the mechanism, and how you would restore the test without weakening the guarantee that actually matters.
answer
- verifySequence = completeness + order
- extra call ⇒ unmatched record ⇒ fail
- read the error: extra call vs missing call
- verifyOrder tolerates gaps
- split the interface, don't just loosen the mode
basics
~20 sverifySequence requires the listed calls to match the referenced mocks' recorded calls one-to-one, in order. The new metrics call is an unmatched record, so the block fails. Fix it by moving to verifyOrder for the contractual order, or by narrowing what the mock exposes.
solid answer
~60 s`verifySequence` is the strictest MockK mode: for the mocks referenced in the block, the recorded calls must line up with the listed calls **exactly** — same number, same order, one-to-one. It is not asserting "these things happened in this order"; it is asserting "this is the complete interaction". Any new call on that collaborator, however incidental, leaves a record with no matching entry, and the block fails even though nothing about the original contract changed. The repair depends on what the test is really protecting: - If only the *relative* order matters (write inside the transaction, flush after the last send), switch to `verifyOrder`, which tolerates gaps and only constrains the calls you name. - If completeness matters but order does not, `verifyAll` is the better fit. - If the collaborator is genuinely two responsibilities in one interface, split it so the metrics traffic lands on a different mock — the block then stops seeing it. - Keep `verifySequence` only for protocol-shaped collaborators where an extra call really is a defect.
code
kotlin · 13 lines// Was: freezes the whole interaction with repo
verifySequence {
repo.findById(1)
repo.save(any())
}
// Now fails because production also calls repo.increment("saved")
// Keeps the guarantee that mattered: save happens after the read
verifyOrder {
repo.findById(1)
repo.save(any())
}go deeper
Explain that verifySequence demands the exact, complete list of calls, so any new call breaks it, and that verifyOrder is the tolerant alternative.
Add the one-to-one matching mechanic and show the concrete swap to verifyOrder, noting that gaps are then ignored.
Diagnose from the error output, choose the mode from the contract on the two axes, and raise the interface-splitting fix as the structural answer.
Set a team-level rule for where exhaustive ordering is warranted — protocol-shaped ports only — and explain how undirected strictness trains people to edit assertions without reading them.
## What actually broke MockK appends every invocation on a mock to a chronological call record. `verifySequence` queries that record with the harshest criterion available: take the recorded calls belonging to the mocks referenced inside the block, and require them to correspond one-to-one and in order with the entries you listed. Not a subsequence — a complete match. So the block is really two assertions welded together: 1. **Completeness** — no recorded call on those mocks is left over. 2. **Order** — the matched calls appear in exactly the listed positions. Adding `metrics.increment(...)` — or a `log`, or an extra `findById` for a new field — on the same mock violates (1) instantly. Nothing about the ordering the test was written to protect has changed; the test failed because `verifySequence` had silently frozen the *entire* interaction, including the parts nobody meant to specify. This is the core brittleness mechanic of exhaustive verification: it converts every future call on that collaborator into a test change. On a narrow, protocol-shaped port that is a feature. On a general-purpose service interface it is a tax paid on every unrelated feature. ## Diagnosing it quickly Read the `AssertionError` message: MockK prints the verification block you wrote and the calls it actually recorded. When the recorded list contains your listed calls in the right order *plus* one extra, the failure is a completeness failure, not an ordering failure. That distinction points straight at the fix. (If instead your listed calls are missing from the recorded list, the problem is matcher mismatch — different arguments — and no change of verification mode will help.) ## Restoring the test **Option 1 — verifyOrder for the contractual part.** Most tests that reach for `verifySequence` only ever cared about causality: the write happened inside the transaction, the flush came after the sends. `verifyOrder` expresses that as a subsequence query, ignoring anything you did not list. The new metrics call falls into a gap and the test survives. ```kotlin verifyOrder { tx.begin() repo.save(any()) tx.commit() } ``` **Option 2 — verifyAll when the set, not the order, is the contract.** If the point was "this handler touches the repository exactly twice", `verifyAll` keeps the completeness guarantee and drops the order requirement. It still fails on the new metrics call if metrics lives on the same mock, so this only helps when the churn is ordering, not extra calls. **Option 3 — narrow the collaborator.** The deepest fix is usually design, not DSL. A collaborator that carries both domain operations and telemetry is doing two jobs; splitting the interface puts the metrics traffic on a separate mock, which the block never references, so exhaustiveness stops seeing it. This is the option that keeps the strong guarantee *and* stops the churn. **Option 4 — exempt genuinely incidental traffic.** MockK can drop nominated calls from the record so they never count against exhaustive checks. Use it sparingly and with a comment, because a muted call is also invisible to any later assertion about it — you have traded a noisy test for a blind spot. **Option 5 — keep verifySequence deliberately.** Some collaborators really are protocols: a transaction manager, a stream that must be opened before written and closed after, a builder-style API, a hardware or wire protocol where an unexpected extra call is itself a bug. There, the test failing on the new call is the system working as designed, and the right response is to update the block and ask whether the new call belongs there at all. ## The judgement to articulate The question an interviewer is probing is whether you know that `verifySequence` specifies a *complete* interaction rather than an ordering, and whether you can pick a mode from the contract instead of from habit. Two axes decide it: does completeness belong in the contract, and does order belong in the contract? Answer both and the mode follows — `verify` (neither), `verifyOrder` (order only), `verifyAll` (completeness only), `verifySequence` (both). A secondary point worth making: brittleness is not inherently bad. A test that fails when someone adds an unreviewed call to a payment gateway is doing its job. The failure mode to avoid is *undirected* strictness — `verifySequence` applied everywhere by default, so the suite screams on changes nobody considers meaningful, and the team learns to edit assertions without reading them.
- When is verifySequence the right choice rather than a brittleness smell?When the collaborator is a protocol and an unexpected call is itself a defect: open-write-close on a stream, begin-write-commit on a transaction manager, a wire or hardware driver, a builder that must be configured before build(). In those cases completeness is part of the contract, so a failure on a new call is the test doing its job — you update it deliberately and ask whether the new call is legitimate.
- How do you tell an ordering failure from a completeness failure in MockK's error output?Compare the verification block MockK echoes with the recorded call list it prints. If your listed calls all appear, in the listed order, and there are extra records, it is a completeness failure and only the exhaustive modes can produce it. If a listed call is absent from the record, it is a matcher or argument mismatch — the call never became a candidate — and changing verification mode will not fix it.
verifySequence is a checklist where every line on the receipt must match yours exactly; verifyOrder is a rule that says the bread must be scanned before the wine — other items on the receipt are nobody's business.
saying these in an interview costs you the question
- Describing verifySequence as "verifyOrder but stricter about order" while missing that it also forbids unlisted calls
- Fixing the failure by deleting the ordering assertion entirely instead of downgrading to verifyOrder
- Assuming an unrelated mock's new call could have caused the failure — exhaustiveness is scoped to referenced mocks
- Muting the new call from the record as a reflex, without noting that it becomes invisible to all later assertions
- Treating all strictness as brittleness and concluding interaction order should never be asserted