When a middleware short-circuits a request instead of delegating, what should an isolated test assert beyond the response status?
answer
- two claims, not one
- did the chain actually stop?
- zero recorded invocations
- status plus the headers it implies
- assert the work that was skipped
basics
~20 sAssert that the stub standing in for the next element recorded zero invocations, that the headers the chosen status implies are present, and that no downstream side effect ran. A status alone does not prove the chain stopped.
solid answer
~40 sA short-circuit is two claims at once: *this hook produced the response* and *nothing after it ran*. The status only supports the first, and weakly — a downstream element could have produced the same code. So the test asserts the recording stub's invocation count is `0`, then asserts the response is complete: the status, plus whatever headers that status implies (`Location` on a redirect, `Retry-After` on a refusal that asks the client to wait, a content type when there is an error body), plus the body shape if the hook writes one. Finally it asserts the absence of the work the chain would have done — a repository untouched, a message not published — because that is what the short-circuit was for.
go deeper
Two assertions, not one: the stub standing in for the rest of the chain was never called, and the response carries the status the hook chose.
Explain why the status is weak evidence on its own: several places can produce the same code, and a hook can set one and still delegate.
Talk about completeness — the header a redirect or a wait-and-retry refusal must carry, the error body shape clients parse — and about asserting the work that was skipped.
Argue for the contract these tests pin down: which refusals the system is allowed to emit, in what shape, and who is permitted to emit them.
When a hook short-circuits, it produces the response itself and never invokes the rest of the chain. An isolated test for that path is cheap to write and easy to write badly: asserting the status code and stopping there leaves most of the contract unchecked. ## A short-circuit makes several claims, not one 1. **The chain stopped here.** Nothing after this hook ran. 2. **This hook produced the response.** Not something downstream that happened to choose the same status. 3. **The response is complete and usable by the client.** A status line on its own is rarely a valid answer. 4. **The work being avoided was actually avoided.** That is generally the *reason* for the short-circuit. Only the third is even partly covered by an assertion on `response.status`, and the first two are exactly what a recording stand-in for the next element exists to reveal. ## The assertion set | Claim | Assertion in the isolated test | | --- | --- | | The chain stopped | the stub recorded `0` invocations | | This hook answered | the status and body were set by the call under test, with the stub never reached | | The answer is complete | the headers the status implies are present, and the body matches the agreed error shape | | The work was skipped | the collaborators the downstream path would have used recorded no interaction | | The hook did not also continue | the count is `0`, asserted after the call returns, not mid-flight | ## Why the status alone is a weak assertion Most refusal codes are producible from more than one place. A `403` can come from the hook under test or from an authorization check deeper in; a `404` can come from a hook or from the absence of a route; a `500` can come from anywhere at all. If the only assertion is the code, then a hook that delegates and lets something further down produce the same code passes the test — and the whole point of a short-circuit, which is that the rest never runs, goes unverified. The invocation count is what separates *the right answer* from *the right answer for the wrong reason*. The complementary failure is subtler: a hook that sets a status **and then delegates anyway**. Setting a status does not stop a chain; returning without invoking the continuation does. In that shape the response may still end up carrying the intended code — so the status assertion passes — while every downstream effect the refusal was meant to prevent has already happened. That bug is invisible to any assertion except the count. ## Completing the response A refusal that a client cannot act on is a half-implemented refusal, and the isolated test is the cheapest place to pin the details down: - **A redirect needs its target.** A `3xx` with no `Location` header is not a redirect; the test asserts both. - **A refusal that asks the client to wait needs to say how long.** When the response is `429` or a `503` produced by a shedding hook, `Retry-After` is part of the answer. - **An error body needs its content type.** If the hook writes a structured error, the test asserts the content type and the field names, because that shape is the contract the client parses. - **A response may need to say nothing.** Some short-circuits are deliberately bodiless; assert that too, rather than leaving it unspecified. ## Asserting the absence The strongest assertion in a short-circuit test is often negative: the thing that was supposed to not happen did not happen. If the hook was constructed with test-controlled collaborators, assert they recorded nothing. If the recording stand-in for the chain is the only downstream, its zero count already *is* that assertion — which is the elegant part: one recording covers both 'the chain stopped' and 'the work was skipped', because in this fixture the chain is the only route to the work. ## Shaping the test so it keeps working - **Write one test per short-circuit reason**, not one test with several requests; when a refusal changes status, exactly one test should go red. - **Read the outcome from wherever the framework puts it.** Where the hook returns a response the test inspects the returned value; where it writes into a mutable response the test inspects that object. The assertions are the same; the handle differs. - **Keep the delegating counterpart next to it.** A refusal test that is green because the hook refuses *everything* is a common and embarrassing failure; the paired test that asserts `callCount == 1` for a valid request is what stops it. - **Do not assert on timing or ordering of internal calls** to prove the short-circuit. The count and the response are the observable contract; anything finer is a test of the hook's private structure.
- A hook sets a status and then delegates anyway. Which assertion catches it?Only the invocation count. The response may still carry the intended status, so a status assertion passes while every downstream effect the refusal was meant to prevent has already run. Asserting `callCount == 0` is what fails.
- A hook short-circuits with `429`. What does the test assert alongside the status?That `Retry-After` is present and carries the value the hook computed, that the body matches the agreed error shape if one is written, and that the stub continuation recorded zero invocations.
- Why keep a delegating test beside every short-circuit test?Because a hook that refuses every request passes all its refusal tests. The paired test asserts `callCount == 1` and an untouched response for an acceptable request, pinning down the boundary between the two paths.
saying these in an interview costs you the question
- Asserting only the status code and calling the short-circuit covered.
- Believing that setting a status is what stops the chain.
- Forgetting to assert the next element was never invoked.
- Assuming a refusal needs no headers beyond the status line.
- Writing only refusal tests, so a hook that refuses everything stays green.
- Checking the error body text while ignoring its declared content type.