Which collaborator interactions are worth verifying, and which weld the test to the implementation?
answer
- Ask who outside could notice the call
- Commands across a boundary, not internal steps
- Would a behaviour-identical rewrite still pass?
- The result may already carry the evidence
- Assert the stand-in's end state instead
basics
~20 sVerify interactions that are themselves the observable behaviour - outgoing commands crossing a boundary someone depends on. Do not verify internal queries or step-by-step sequencing; assert their consequences instead. If a behaviour-preserving refactor breaks the assertion, it pinned implementation.
solid answer
~50 sI ask two questions of each candidate call. First, could anything outside the unit notice it? A job handed to a queue, an event published, an audit record written or a credential scope narrowed is observable; a private helper or a configuration getter is not. Second, is verifying it the only way to see it? If the collaborator returns data the unit uses, the unit's own result already carries the evidence, and asserting the result proves both that the call happened and that its answer was handled correctly. A call that passes both earns a verification; anything else is better checked through its consequence. The blunt version for reviewing an existing suite is the refactor probe: would a competent, behaviour-identical rewrite still pass? When splitting a method turns dozens of verifications red with no defect present, those assertions were describing the code, not the behaviour.
go deeper
Know the basic split: calls that leave the unit and change something elsewhere may be worth verifying; calls to internal helpers are not. If a collaborator just returns data, assert the result instead.
Be able to apply the rule to a concrete method and justify each verdict, and to explain why verifying a call whose value already shapes the asserted result adds no information while adding a failure mode.
Bring the refactor probe and a worked story: a case where a behaviour-preserving change broke a pile of verifications, and a case where a verification - often a negative one at a safety seam - caught a defect nothing else could see.
Own the second-order cost. A suite that verifies by default freezes the design and trains people to update expectations without reading them; name the default you would set, the seams that are exempt, and how you would migrate an existing suite without stalling delivery.
The coverageArea question behind this one is simple to state and hard to apply: interaction verification is the only way to see some behaviour, and it is also the fastest way to weld a test to the shape of the code. The discipline is deciding, per call, which of those two you are getting. ## The test that decides it Two questions, asked of each call you are considering verifying. **1. Could anything outside the unit notice this call?** An outgoing command that crosses a boundary someone else depends on - a job handed to a queue, an event published, an audit record written, a credential scope narrowed - is observable behaviour. Somebody downstream would notice if it stopped happening. A call to a private helper, a formatting routine, or a getter that reads configuration into a local variable is not observable by anyone; it is a step in *how* the answer is produced, and a competent rewrite is free to remove it. **2. Is verifying it the only way to see it?** If the call returns data the unit uses, the unit's result already carries the evidence. Asserting the result proves the query happened *and* that its answer was handled correctly, in one assertion that survives refactoring. Verifying the query on top of that adds no information and doubles the reasons to fail. A call that passes both questions earns a verification. A call that fails either one is better tested through its consequence, or not tested here at all. ## The refactor probe There is a blunter version of the same test, useful when reviewing an existing suite: **would a competent rewrite of this method, with identical behaviour, still pass?** Rename an internal helper, split the method in two, batch two calls into one that takes a list, reorder two independent steps - if the test goes red, the assertions were describing the implementation rather than the behaviour. That probe is also how you read a suite's history. On an eleven-person team, a refactor that split one dispatcher method into two and changed nothing observable knocked over 47 verification assertions across the suite. None of them was a defect; every one had to be rewritten to match the new call layout. The value those assertions had been providing over the preceding year was close to zero, and their cost arrived all at once. Meanwhile the one verification on that unit that *did* earn its keep was the negative one - that a viewer-level upload never reaches the privileged re-encode path - which caught a genuine permission escalation when a later change moved the credential narrowing after the dispatch instead of before it. Same technique, opposite value, and the difference was entirely whether the asserted call was observable behaviour or an implementation step. ## What over-coupling actually costs Three things, in order of severity. It costs **refactor friction**, so the code ossifies around its tests. It costs **signal**: when a suite routinely goes red for changes that broke nothing, people stop reading failures and start pattern-matching them into "just update the expectation". And it costs **truth in coverage**: a test whose only assertions mirror the method body cannot fail for a behavioural reason at all, so the requirement it claims to protect is in fact unprotected. ## The escape routes When you decide a verification is not the right assertion, you need somewhere to go. - **Assert the consequence.** For a query-shaped collaborator, assert the returned result of the unit. - **Assert the stand-in's state.** A lightweight working implementation of the collaborator can hold what it received, and the test asserts that end state - one job for that asset - rather than the call that produced it. Batching, reordering and helper extraction leave that assertion untouched. - **Move the check outward.** If nothing observable happens inside this unit, the interesting behaviour may live one level up, at the seam where the effect actually leaves the system. - **Redraw the boundary.** A unit whose only observable behaviour is "it calls these six things in this order" is often a unit that has no behaviour of its own. That is a design signal, not a testing problem. ## Keeping the verifications you do need cheap For the calls that survive both questions: verify at a stable interface the team owns rather than at an incidental helper; constrain only the arguments the requirement names; skip order assertions unless the ordering is the rule; and reserve exhaustive "no other interactions" checks for the seams where an unexpected extra call would be a real incident. ## What a senior answer sounds like Not "mock less" and not "verify everything important". It is a rule you can apply to a specific call in a specific test, an admission that the technique buys real coverage at a real price, and at least one worked example of each verdict - one call you would verify because nothing else can see it, and one you would delete because the result already proves it.
- A test verifies six calls and asserts nothing about the outcome. What would you do with it?Treat it as over-specified. Keep the one or two verifications on calls that cross a boundary somebody outside would notice, and replace the rest with an assertion on the unit's result or on the state the stand-in ended up in. If it turns out there is no outcome worth asserting at all, that is a signal the unit has no behaviour of its own and the boundary is drawn in the wrong place.
- How do you keep a verification you genuinely need from breaking on every refactor?Verify at a stable interface the team owns rather than at an incidental helper; constrain only the arguments the requirement actually names, capturing large arguments and asserting the fields that matter; skip ordering unless the sequence is the rule; and reserve exhaustive no-other-interactions checks for seams where an unexpected call would be an incident.
- Does a test breaking during a refactor ever mean the refactor was wrong?Sometimes, and telling the two apart is the skill. Ask what the assertion claims: if it describes an effect an outside observer would notice, a red test means the refactor changed behaviour and should be investigated. If it describes internal call structure that nobody outside can see, the refactor was fine and the assertion was measuring the wrong thing.
saying these in an interview costs you the question
- Verifies every call the method makes and calls it thorough
- Says a refactor-broken verification proves the refactor unsafe
- Re-verifies a query call whose result is already asserted
- Keeps a verification only because deleting it lowers coverage
- Cannot name what an outside observer would notice
- Answers only 'mock less' with no rule for a specific call