When verifying a collaborator's calls, what do count, argument and order assertions each prove?
answer
- Each dimension narrows what may pass
- Never, at least once, exactly n
- Exact value, loose matcher, or captured
- Order assertions are the brittle ones
- Constrain only what the requirement names
basics
~20 sCount assertions prove how often a method ran; argument assertions prove what was sent; order assertions prove one call preceded another. Order is the most brittle of the three - assert it only when the ordering itself is the requirement.
solid answer
~50 sA verification has several independent dimensions, and each one you constrain narrows what passes and widens what breaks. **Occurrence and count** - at least once, exactly n, at most n, never - encodes idempotence, retry budgets and prohibitions; *exactly once* is what catches a duplicate, and *at most three* is what catches a runaway retry loop. **Arguments** can be matched with a catch-all, matched exactly, or captured and asserted field by field; a catch-all makes a verification look stricter than it is, and capture is best for large arguments where only a couple of fields matter. **Order** can be relative or strictly consecutive, and it is worth asserting only when reversing the calls would be a real defect - narrowing a credential scope before dispatching work, for instance. Some libraries also offer an exhaustive *no other interactions* check, which turns the verification into a whitelist: right at a safety-critical seam, expensive everywhere else.
code
pseudocode · 11 linesqueue = recordingStandIn(JobQueue)
scope = recordingStandIn(CredentialScope)
dispatcher = Dispatcher(queue, scope)
dispatcher.onUploadFinished(assetId = "a-8317", role = "viewer")
assert scope.callsTo("narrowTo").count == 1
assert scope.callsTo("narrowTo").first.arg("role") == "viewer"
assert queue.callsTo("enqueue").count == 1
assert scope.callsTo("narrowTo").happenedBefore(queue.callsTo("enqueue"))
assert scope.callsTo("elevate").count == 0go deeper
Know that a verification can say more than 'this was called': it can also say how many times and with what. Be able to explain why 'called at least once' would not catch a duplicate.
Be ready to walk through all four dimensions - occurrence and count, arguments, order, exhaustiveness - and to pick the right level for a stated requirement rather than reaching for the tightest one available.
Show that you derive the assertion from the requirement sentence and leave everything else free. Give a concrete case where an ordering assertion was genuinely load-bearing and one where it was incidental and had to go.
Own the defaults for a whole suite: which seams justify exhaustive and ordered checks, how argument matching is expected to be written so failures are readable, and how you stop over-specified verifications becoming the team's dominant source of red builds.
A verification is not one thing. It is a set of dimensions you may choose to constrain, and each one you add narrows what a passing test permits and widens what a passing test forbids. Choosing well is most of the skill. ## 1. Occurrence and cardinality The weakest form is *it happened at least once*. Stronger forms are *exactly n times*, *at most n times*, and *never*. Cardinality is not decoration - it is often the requirement itself: - **Exactly once** encodes idempotence at the seam. In a video-transcoding queue, a finished upload must produce one job, not two; an at-least-once verification passes on a duplicate enqueue, which is the exact defect worth catching. - **At most n** encodes a retry budget. If the policy is three attempts, a test that only asserts "at least once" says nothing about a runaway loop that calls the downstream service nine times. - **Never** encodes a prohibition. It is the strongest safety statement you can make about a code path, and the easiest to get a false pass from: if the method name or argument you are matching can never match anything, the assertion passes for the wrong reason. Always pair a never-called assertion with a positive test that drives the same call, so you know the check is capable of failing. ## 2. Arguments Three levels of precision, in ascending order of what they prove: - **A catch-all matcher** (any value) proves only that the call shape was used. It is honest when the argument genuinely does not matter, and dishonest when it is used to make a test go green - the verification then reads as if arguments were checked when nothing was checked at all. - **Exact matching** compares the argument against an expected value. This depends on the argument type having meaningful value equality; where equality is identity-based, an exact match silently becomes an identity check and fails for an equivalent object. - **Capture and assert** records the argument that was actually passed and lets you assert on it with ordinary assertions. This is the right tool for a large constructed argument where only two fields carry meaning - assert those two, ignore the rest, and get a failure message that names the field that was wrong instead of dumping two objects side by side. The general rule: constrain the parts of the argument the requirement talks about, and leave the rest free. A verification that pins every field of a job descriptor breaks the first time an unrelated field is added. ## 3. Order By default, verifications are unordered: each says a call happened, none says when. Ordering can be added as *relative order* (this call preceded that one, other calls allowed in between) or as *strict consecutive order* (nothing else in between), and it can span more than one collaborator. Order assertions are the most brittle thing in this toolbox, because most call sequences are incidental - the method happens to do A before B, and doing B before A would be equally correct. Assert order only when **the ordering is itself the requirement**. Real examples: a resource must be opened before it is written; a transaction must begin before work is done; and, in the dispatcher above, the caller's credential scope must be narrowed *before* the job is handed to the worker. Swapping those two lines is a permission escalation - a job briefly queued with elevated rights - and no state assertion anywhere would notice, because the end state is identical either way. That is a sequencing rule worth pinning. ## 4. Exhaustiveness Some libraries offer a check meaning *and no other calls were made on this collaborator*. This converts the verification list into a whitelist: any new outgoing call fails the test even if it is harmless. At a safety-critical seam - the credential collaborator above, an audit sink, a payment client - that is exactly what you want. Applied by default across a suite of a few thousand tests, it is expensive: every ordinary addition breaks a scattering of unrelated tests, and a team of eleven spends its review time relaxing assertions rather than reading them. ## Choosing the level Work backwards from the requirement sentence and constrain only what the sentence constrains. - "Every finished upload enqueues one job for that asset" -> exactly once, with the asset identifier matched exactly, arguments beyond it left free, no order assertion. - "Retries stop after the third attempt" -> at most three, arguments free. - "Credentials are narrowed before dispatch" -> two calls plus a relative-order assertion. - "A viewer-role upload never reaches the privileged path" -> a never-called assertion, plus a sibling test proving the privileged path is reachable at all. Over-specification is the common failure. Each unnecessary dimension is a future false alarm; each missing one is a defect the suite cannot see. Interviewers are checking that you reason about this per requirement rather than reaching for the tightest assertion available.
- When is capturing an argument and asserting on it better than matching it inline?When the argument is a large constructed object and only a field or two carry the requirement, or when its equality is identity-based so an inline exact match would fail for an equivalent value. Capturing lets you assert the two fields that matter with ordinary assertions and get a failure message naming the wrong field instead of two whole objects printed side by side.
- What does an assertion that a collaborator received no further calls buy you, and what does it cost?It converts your verifications into a whitelist: any outgoing call you did not list fails the test. That is valuable at a seam where an unexpected extra call would be an incident - a credential or audit collaborator. Across an ordinary suite it is expensive, because every harmless addition breaks a scattering of unrelated tests and the team learns to relax assertions rather than read them.
- Why is a never-called assertion the easiest one to get a false pass from?Because it succeeds whenever nothing matched, and nothing matches for many reasons that have nothing to do with correct behaviour: a mistyped method, an argument matcher that can never match, or a test that never reached the branch. Pair it with a positive test that drives the same call so you have evidence the assertion can fail.
saying these in an interview costs you the question
- Asserts strict order on calls that are genuinely independent
- Uses a catch-all matcher then claims arguments were checked
- Verifies at-least-once when the rule is exactly once
- Believes a never-called assertion cannot pass by accident
- Adds a no-other-interactions check to every test by default
- Pins every field of a large argument object