Your web-layer slice tests are green, yet the deployed route fails on first real use — how does that happen and what do you change?
answer
- the double was never the real object
- construction and configuration untested
- the request may not reach the handler
- prove the graph builds once
- replace the external edge, not the next layer
basics
~20 sThe slice supplied a test double where production must build a real collaborator, so that collaborator's construction, configuration and registration were never exercised. Add a test that boots the whole application on real wiring, faking only true externals.
solid answer
~50 sA slice proves the HTTP surface: the route matched, the request bound, the response serialised. It says nothing about the object underneath the handler, because the test put one there itself. In production that object has to be built by the real wiring: a registration must exist, its configuration must resolve, its own dependencies must be constructible, and start-up validation must pass. Any of those can be missing while every slice test stays green, and the first real request then fails — often as an error the handler never sees. The fix is to add one test per application, or per route family, that boots the whole graph with only genuine externals replaced, plus a start-up test that simply proves the graph builds. Then keep replacement narrow: swap the external edge, not the layer directly under the handler.
go deeper
Remember the core sentence: a test double means the real object was never built. Anything needed to build it — a registration, a configuration value, its own dependencies — is untested by that test.
Enumerate the concrete fault classes: missing registration, unresolved configuration, an uninstantiable dependency, skipped start-up validation, and a double kinder than the real implementation.
Show the diagnosis order and the minimal fix: read the start-up failure first, check whether the request reached the handler, then add one boot test plus a thin real-wiring request per route family rather than widening the slow tier.
Own the residual risk explicitly. Something is always replaced, so the deliverable is a stated boundary: what the suite proves, what it assumes, and which failures will therefore only ever be seen in a deployed environment.
## The shape of the failure Every web-layer slice makes the same trade: it keeps the framework's request machinery and replaces what sits beneath the handler. That replacement is the reason the tier is fast, and it is also a hole of a very specific shape — **the code that produces the real collaborator never runs**. A green slice suite plus a failing endpoint is not a contradiction; it is the expected outcome when the fault lives in that hole. What makes it sting is the timing. The failure usually appears at application start-up or on the first request, is loud, and is often not attributable to the handler at all — the request may never reach the handler code that all those green tests exercised. ## Why a green slice is compatible with a broken route 1. **No registration for the real implementation.** The slice registered a double; nothing ever required a production binding to exist. 2. **Configuration that does not resolve.** The real object needs a value — an address, a credential, a timeout — that is absent or malformed in the deployed configuration. The double needed nothing. 3. **A dependency of the dependency.** The real collaborator is constructible only if its own dependencies are, and that chain was never walked. 4. **Start-up validation.** Many frameworks validate the graph, the route table or the configuration schema during start-up; a narrow slice may skip those checks entirely. 5. **A double that is kinder than the real thing.** It returns a value where the real implementation throws on an unreachable resource, or accepts an argument the real signature rejects at run time. 6. **Chain differences.** Hooks registered only in the full application — security, correlation, error translation — were absent from the slice, so the request now takes a path no test has seen. ## Diagnosing it once it happens - Read the failure at **start-up** first. A graph that fails to build produces a different signature from a handler that throws, and the difference immediately tells you which half of the system to look at. - Establish whether the request reached the handler at all. If it did not, the fault is in wiring, configuration or the chain, and no amount of handler-level testing will reproduce it. - Re-read what the failing route's slice actually replaced. The replaced boundary is the first suspect, every time. - Compare the configuration the slice used with the deployed one. Slices often carry a minimal test configuration that quietly satisfies requirements the real one does not. ## Closing the gap - Add a **start-up test**: boot the whole application on real wiring and assert that it starts. It catches missing registrations, unresolvable configuration and failed validation, and it costs one boot for the entire suite. - Add a **thin full-stack test per route family**: one request per critical route through the real graph, replacing only genuine externals such as a remote service. Assert the status and a minimal body shape; the detailed contract assertions stay in the slice. - **Move the replacement boundary outward.** Replacing the object directly under the handler hides the most wiring; replacing only the external edge keeps the intermediate layers real and keeps the hole small. - **Prefer a fake that behaves like the real thing** over one that always succeeds. A double that cannot fail teaches the suite that failure is impossible. - Make the full-stack tier **narrow but present**. Its job is not coverage; it is the single proof that the real graph builds and that one real request survives it. ## What not to conclude The wrong lesson is to promote every slice test to the full stack. That multiplies the slowest tier by the number of cases, and buys almost nothing: the wiring proof does not get stronger by being repeated a hundred times, while the suite becomes slow enough that people stop running it, which costs more than the defect did. The right lesson is that each tier answers a different question — the slice answers "is the HTTP contract right?", the full stack answers "does the thing we actually deploy assemble and respond?" — and a suite with no answer to the second question will keep shipping this exact failure. A strong answer also names the residual risk honestly: even the full stack usually replaces something. The goal is not zero replacement but knowing precisely what was replaced, so that when an endpoint fails in production you can say in one sentence which assumption broke.
- Why is a start-up test that only asserts the application boots worth its runtime?It catches the whole family of construction faults at once: missing registrations, unresolvable configuration, uninstantiable dependencies and failed graph validation. It costs one boot for the entire suite, needs no request and no assertions beyond starting, and it fails with a message that names the broken component directly.
- How does the choice of replacement boundary change how much wiring stays untested?The closer the double sits to the handler, the more real layers it erases. Replacing the object the handler calls removes every layer beneath it from the test; replacing only the external edge keeps the intermediate construction, configuration and collaboration real, so the untested region shrinks to the external hop itself.
- Should every slice test be promoted to the full stack after an incident like this?No. The wiring proof does not strengthen by repetition, and multiplying the slowest tier by every case produces a suite people avoid running. Add breadth at the full-stack tier only where a route family's wiring is genuinely distinct, and keep the detailed contract assertions in the slice.
saying these in an interview costs you the question
- Concludes the framework is at fault rather than the replaced boundary
- Promotes every slice test to the full stack after one incident
- Uses a double that can never fail, then calls the path tested
- Assumes a green slice implies the application starts
- Ships a minimal test configuration that hides a missing production value