One Claude Code Tip a Day: Add Tests Around the Seam
Use Claude Code to add small guardrail tests around the seam you are about to change: read behavior first, write characterization tests, verify failure signals, then edit with confidence.
This post is part of the “One Claude Code Tip a Day” series — a daily guide to using Claude Code more effectively.
The most expensive Claude Code mistake is not a bad patch. It is a patch that looks reasonable because there was no test close enough to prove it wrong. You ask for a small feature, Claude reads the obvious file, edits three functions, reports success, and the diff feels clean. Two days later, a customer finds the edge case that lived in a branch nobody tested.
Today's habit: when you touch untested or lightly tested code, ask Claude Code to add tests around the seam before it changes the seam. Not a giant test suite. Not a philosophical rewrite. A small guardrail that captures the behavior you are about to rely on.
Start with the risky seam, not the test command
Imagine a Node service has a subscription webhook handler. It accepts Stripe events, updates a user record, and sends a notification. You need to add support for a new `trial_will_end` event. The code has one broad integration test, but no focused coverage for event routing.
A shallow prompt would be:
`Add tests for the webhook handler.`
That sounds responsible, but it gives Claude too much freedom. It may write tests for the happy path you already understand, mock the wrong layer, or create brittle snapshots that pass without proving anything. Instead, start with a read-only pass:
`Read the webhook handler, existing tests, and test helpers. Do not edit yet. Identify the smallest seam where a new trial_will_end event should be routed. Tell me which current behavior needs a guardrail test before we change it, and which command will run only that test file.`
This prompt forces three useful outputs: files inspected, the seam, and the verification command. If Claude cannot name those, it is not ready to write a test.
Ask for a characterization test first
Before adding the new event, lock the old behavior that might break. For the webhook example, the risk is that a routing change accidentally alters `invoice.payment_failed` or `customer.subscription.deleted`. The first edit prompt should be narrow:
`Add one characterization test around the webhook event router. It should prove that invoice.payment_failed still calls the existing failed-payment path and does not call the trial-ending path. Use the existing test style. Do not implement trial_will_end yet. Run only the focused test command and show the assertion names.`
The important phrase is “do not implement yet.” You are separating observation from change. Claude Code is good at finding local test conventions, but it needs a contract: protect current behavior first.
After it edits, inspect evidence before trusting the summary:
`git diff -- tests/webhook-handler.test.ts src/webhooks/handler.ts`
`npm test -- webhook-handler`
A useful test has a clear failure signal. If the assertion merely checks that “some response is 200,” it will not catch the bug you care about. Ask for a second pass:
`Review the new test as if it failed in CI. What exact regression would it catch? What important regression would it miss? Do not edit yet.`
That review often reveals the difference between coverage and confidence.
Then add the new behavior under the guardrail
Once the characterization test passes, add the new feature in a separate step:
`Now implement trial_will_end routing with the smallest change. Keep the existing failed-payment and subscription-deleted tests passing. Add one focused test for the new event. Run the same focused test command, then show git diff --stat and the relevant diff.`
This sequence changes the shape of the session. Claude is no longer improvising a feature and retrofitting tests afterward. It is editing inside a small verified boundary. If the new test fails, you know whether the problem is in the new route, the mocks, or the old assumption.
Failure modes to watch for
The first failure mode is mock theater. Claude may mock the handler so heavily that the test only proves the mock returns what the mock was told to return. Push it closer to the real seam:
`This test mocks the behavior under test. Rewrite it so the real router function runs, and only external IO is mocked.`
The second failure mode is testing implementation trivia. A test that asserts a private helper was called can break during a harmless refactor. Prefer observable behavior: database update intent, emitted job, returned status, or notification payload.
The third failure mode is unfocused test runs. Claude may run `npm test`, hit an unrelated timeout, and still claim the relevant test passed. Ask for the focused command and the final lines of output. If the project is split into packages, make it name the package:
`Run the exact package-level command for this test file. If it fails before reaching the assertions, stop and show the setup failure instead of continuing.`
Use tests as a conversation tool
The best part of this workflow is not just the test file. It is the way the test changes the conversation with Claude Code. Once a guardrail exists, you can ask better questions:
`Which assertion protects the old behavior? Which assertion proves the new event? What code could still regress without failing these tests?`
If the answer is vague, the tests are vague. If the answer names concrete assertions and paths, you have a reviewable safety net.
Rule of thumb
Do not ask Claude Code to “add tests” as a generic cleanup task. Ask it to add one guardrail around the seam you are about to change, verify that guardrail with a focused command, then make the feature or refactor in a second pass. A small test that proves the right thing is more valuable than a broad test file that only makes the coverage number feel better.