AI coding tools can write a test file in seconds. Most developers have also noticed the catch: the tests are green, coverage goes up, and a real bug ships straight through them.
That usually isn’t the model being bad at testing. It’s the prompt leaving out everything that makes a test valuable. Here are five changes I now make to every testing prompt — in Cursor, Claude Code, ChatGPT or Copilot Chat — and why each one matters.
1. Give it the intent, not just the code
If the only input is the implementation, the implementation becomes the spec. The model reads what the code does and asserts exactly that — including any bug.
Add one or two sentences of intent, in your own words:
What this code is supposed to do:
prorate() returns the charge for the unused part of a billing period,
rounded half-up to cents. It never returns a negative amount.
Now the model has something to test against. If it notices the code can return a negative amount, it has a reason to flag it instead of pinning it.
2. Ask for a plan before any code
“Write tests” invites volume. Ask for a behaviour inventory and a case table first:
Before writing any code:
1. List every observable behaviour (return values, errors, side effects, branches).
Mark anything that looks like a bug with [SUSPECT].
2. For each behaviour, list cases: happy path, boundaries (empty, zero, one, max,
off-by-one), invalid input, error paths.
Show me both. Then write the tests.
A case table takes ten seconds to review. If the boundaries or error paths are missing, you see it immediately — instead of discovering it in 300 lines of generated code.
3. Attach one existing test file
Describing your conventions (“we use Vitest, factories live in tests/factories, we name tests like…”) works poorly. Showing them works well:
Conventions to copy: tests/billing/invoice.test.ts
Match its imports, helpers, file location and naming.
This one line stops most of the “new assertion library, different folder, duplicate fixtures” problems.
4. Write the rules the model would otherwise break
Models take the shortest path to green. State the boundaries explicitly:
Rules:
- Do not change production code to make tests pass. If the code seems wrong, report it.
- Expected values must be hand-computed literals, not the same formula as the code.
- Only stub true boundaries: clock, randomness, network, third-party APIs.
- No fixed sleeps. Freeze time and seed randomness explicitly.
The second rule alone removes a whole class of “mirror tests” — tests that agree with the implementation by construction and can never fail for the reason you care about.
5. Make it justify every test
End with a self-check:
For each test, state in one line the specific bug it would catch.
Delete any test whose answer is "none" or "same as another test".
List behaviours from the case table that still have no test.
Then do the human version: break the code on purpose (flip a < to <=, return early) and see whether anything goes red. If nothing does, the tests are decoration.
Different jobs need different prompts
The five changes above are the skeleton. The process section should change with the job:
- Integration/API tests: rank scenarios by risk — contract, validation, auth (including another tenant’s resource), persistence, idempotency, downstream failure — and forbid mocking the database.
- E2E: separate checkpoints worth asserting from incidental UI, use role/label locators, and ask for an explicit flake review.
- Legacy code: write characterization tests that pin current behaviour, bugs included, before you change anything.
- Flaky tests: ask for ranked hypotheses with evidence and a reproduction step. Retries and longer sleeps are not fixes.
- Coverage: treat the percentage as one input to a risk-based plan, not the goal.
The shape that works
Every good testing prompt I use has the same five parts:
| Part | Purpose |
|---|---|
| Role | “Senior engineer writing tests a skeptical reviewer will accept” |
| Context | Target, intent/spec, framework, one example test, what not to touch |
| Process | Numbered steps, plan before code |
| Output contract | Exact sections and tables to return |
| Rules | The shortcuts the model must not take |
Try it on a function you know well. If the case table finds an edge case you hadn’t written down, it’s working.
I packaged the eight prompts I use for this — unit, spec-first, integration/API, E2E, characterization, flaky diagnosis, coverage planning and mock boundaries — plus two checklists, a Cursor rule and a PDF guide, as the **AI Test Generation Prompt Pack: https://lirenplr.gumroad.com/l/cuofwr. Everything above works without it.
— Pluto · @Mars_Peakul
Top comments (0)