Claude Code will write tests for anything you ask it to. The problem is that unconstrained, it writes tests that confirm what the code does rather than tests that catch when the code breaks.
Here's the testing strategy that's worked for me after several months of daily use.
Test behavior, not implementation
The prompt matters:
Bad: "Write tests for this function"
Good: "Write tests that verify the behavior of this function from a caller's perspective. Don't test internal implementation details."
When Claude tests implementation details, every refactor breaks your tests even when nothing actually broke. Tests should survive refactoring.
Lead with edge cases
Claude's default is to write the happy path first and add edge cases as an afterthought. Flip this:
Write tests for [function]. Start with edge cases:
- Empty/null inputs
- Boundary values (0, -1, max int)
- Invalid types
- Concurrent calls (if applicable)
Then add the happy path.
Edge cases are where real bugs live. If Claude writes them first, they get proper attention instead of becoming // TODO: add edge case tests.
One assertion per test
Claude will pack multiple assertions into a single test unless you stop it. Multiple assertions mean when a test fails, you don't know which assertion failed without reading the stack trace.
Add to your prompt: "One assertion per test. If you need to verify multiple things about the same scenario, split them into separate tests."
What not to test
Tell Claude explicitly what's out of scope:
- Don't test library code (React renders, database queries — test your logic, not theirs)
- Don't test constants and config values
- Don't test getter/setter trivially
- Don't mock everything — if you're mocking more than one thing per test, your function probably does too much
The test review prompt
After Claude writes tests, I run this:
Review these tests. For each test, tell me:
1. What scenario is it testing?
2. Would it catch a bug where [describe likely failure mode]?
3. Is there anything it claims to test but doesn't actually verify?
This catches the "test that always passes" problem. Claude will sometimes write tests that assert something trivially true regardless of the implementation.
Integration tests vs unit tests
I use Claude for unit tests almost exclusively. Integration tests require knowing your specific environment, deploy setup, and data shapes — context that's hard to give Claude in a single prompt.
For integration tests I write a rough outline and ask Claude to fill in the assertions, not the setup.
The coverage trap
High test coverage from Claude-generated tests is not necessarily a good thing. 100% coverage with tests that don't catch real bugs is worse than 60% coverage with tests that do.
When I ask Claude to increase coverage, I now add: "Only add tests that would catch a real bug. If a test can't fail due to a realistic code change, don't write it."
If you want more patterns like this, the Agent Prompt Playbook has a full section on testing prompts — including prompts for TDD, mutation testing, and flaky test diagnosis. $29.