Test the failure, not the happy path
Write the test for the timeout, the refusal and the malformed response before the one that passes.
The happy path is the case you already have in your head while writing the code, which is exactly why it is the least valuable thing to test. Everything that has ever woken you up was a path nobody imagined: the call that hung, the response that was almost JSON, the retry that ran twice. Those are cheap to test and nobody does it.
What goes wrong: A dependency degrades rather than dies, your code has no branch for that, and the failure surfaces three services away.
You are violating it when
- Every test in the file asserts success.
- You have no test that asserts what happens on a timeout.
- A network error in a dependency produces a stack trace with your own function at the top.
The usual objection: That coverage measures this. Coverage counts lines executed, and the line you never wrote — the error branch — cannot be counted as missing.
A useful test suite starts by making the uncomfortable cases explicit: the dependency that never answers, the API that refuses a request, the body that looks structured but cannot be parsed, and the retry that happens more than once. These cases are not edge decorations around the real behavior. They are often the behavior that decides whether a service degrades locally or exports confusion to the next service in the chain.
This works because failure tests force the code to have a real branch for degraded dependencies. Timeouts need deadlines and cancellation. Refusals need classification and handling. Malformed responses need safe parsing and clear errors. Retries need idempotency or another guard against duplicate effects. Without a test that provokes the condition, the implementation can appear complete while silently relying on the dependency to fail cleanly, quickly, and only once.
The misconception is that coverage measures this. Coverage can report that existing lines ran; it cannot report that the missing timeout branch, parse-error branch, or duplicate-retry protection was never written. A suite can cover most of the happy path and still prove almost nothing about the cases that wake teams up. A consistently failing test for a real failure mode is a stronger signal than a large coverage number.
When an agent writes the code, this becomes more important rather than less. Agents are good at producing the obvious success flow and plausible mocks, which can make a shallow suite look convincing. Giving the agent the failure test first constrains the implementation toward explicit error handling, observable boundaries, and deterministic behavior instead of a demo that only works when every collaborator behaves perfectly.
Install it
npx klay practices add test-the-failure-path.github/workflows/klay-tests.ymlcreate# Klay practice: test-the-failure-path # https://klaylearn.com/practices/test-the-failure-path # # Runs your test suite on every pull request, and refuses to let a test be # silently skipped or retried into passing. # # The retry check is the part that matters. A test that only passes on the # second attempt is not a passing test — it is a failing test with a longer # timeout, and every "just re-run CI" habit starts there. Detecting it is # cheap; noticing it six months later is not. name: Tests on: pull_request:
The previews are the first lines of each file; the command writes them in full. Existing files are never overwritten.
How you know it stuck
npx klay practices audit reports these checks for this practice:
ci-runs-teststests-present
Where this comes from
- REL05-BP05 Set client timeouts - Reliability PillarFirst-party reliability guidance naming missing timeout handling as a concrete client-side defect.
- references-details-emptyExplains why retry behavior must be designed and verified, not assumed safe.
- Google SRE - Data Integrity: Principles and Best PracticesAdds the SRE case for intentionally provoking failures before incidents do.
- Disasterpiece Theater: Slack’s process for approachable Chaos EngineeringShows a production team systematically enumerating and causing tolerable failures.
- Flaky Tests at Google and How We Mitigate ThemSupplies evidence that unreliable failure tests destroy trust in the suite.
Questions
- Is this not just chaos engineering?
- It is the unit-test-sized version of the same idea, and it costs a few minutes rather than a platform.