Make Claude Test Behaviour, Not Implementation
What it is
A prompting discipline that tells Claude to assert what a unit does from the outside — given these inputs, expect these outputs or effects — rather than how it does it internally. The test describes a contract, not a call sequence.
Why it works
Tests that spy on private method calls, mock everything, and assert 'was X called with Y' turn red the moment you refactor, even when nothing observable changed. That trains a team to ignore the suite. Behaviour tests only fail when the contract actually breaks, so they double as living documentation and as the safety net that makes refactoring cheap. Claude, left to its own defaults, over-mocks; you have to ask for the outside-in version explicitly.
When to use it
Pure functions, service boundaries, API handlers, reducers — anywhere you want to change the internals later without rewriting the tests. Especially before a refactor.
When not to use it
A specific interaction genuinely IS the contract (e.g. 'must call the payment gateway exactly once, and never twice'). There, asserting the call is correct — don't dogmatically avoid it.
Prompt
Write tests for <unit>. Rules:
- Assert observable behaviour: inputs → return value or side effect.
- Do not mock internal collaborators unless they cross a real boundary (network, disk, clock).
- No assertions on private methods or call order unless the order is part of the contract.
List the behaviours you're covering as plain sentences before the code.Example
For a rate limiter, instead of asserting bucket.refill() was called, Claude writes tests that make 5 allowed calls, asserts the 6th is rejected, waits past the window, and asserts it's allowed again — the actual promise the limiter makes.
Advanced version
Have Claude write the behaviour list first as a table of cases (input, condition, expected), get your sign-off on the cases, then generate code. You review the specification, not 200 lines of assertions.
Common mistakes
- Accepting a wall of mocks — it usually means the test is pinned to today's implementation.
- Asserting log lines or call counts as a proxy for behaviour you could assert directly.
- Letting one test cover six behaviours, so a failure doesn't tell you which contract broke.