Tests written against the spec, not against the implementation’s bugs. Below are 7 copy-ready prompts. Fill in the [BRACKETS], copy, and paste into ChatGPT, Claude, Gemini or any capable assistant.
AI writes tests fast, and by default it writes them by reading your implementation — which means it tests that the code does what it does, including the parts that are wrong.
The 7 prompts
Generate tests that find bugs, not tests that pass
Write a test suite aimed at breaking the code.
Write tests for this code. Your goal is to find bugs, not to achieve coverage. LANGUAGE AND TEST FRAMEWORK: [DETAIL] WHAT THIS CODE IS SUPPOSED TO DO: [SPECIFICATION] EXISTING TEST STYLE (if any): ``` [PASTE AN EXISTING TEST, or 'none'] ``` CODE: ``` [PASTE] ``` Before writing tests, list: - The code's implicit contract: what it assumes about inputs and what it guarantees about outputs - The boundaries: every value where behaviour changes Then write tests in these groups: 1. CONTRACT - the stated behaviour works for typical input 2. BOUNDARIES - each boundary from your list, tested on both sides and exactly at it 3. DEGENERATE INPUTS - empty, null/None, zero, negative, single element, maximum size, whitespace-only, unicode, very long strings 4. ERROR PATHS - every way this can fail, and what it should do. Test that it fails correctly, not just that it fails. 5. THE SUSPICIOUS ONES - based on reading this specific code, the three inputs most likely to reveal a bug. Say why you suspect each. Rules: - Test names must describe the scenario and expected outcome: test_returns_empty_list_when_input_is_empty, not test_empty. - No test may assert only 'does not throw'. - Do not test framework or library behaviour. - Do not write a test whose assertion just restates the implementation. - Group 5 is the most important. If any of those tests would fail against this code, say so explicitly before the test code.
What you get: A grouped test suite built around boundaries and suspicious inputs, with likely-failing tests flagged upfront.
Tip: Group 5 is where the value is. Asking for the tests most likely to fail turns test generation into a code review.
Find what your test suite is not covering
Identify the gaps that a coverage percentage hides.
Analyse the gaps in this test suite. Line coverage is not the question. CODE UNDER TEST: ``` [PASTE] ``` EXISTING TESTS: ``` [PASTE] ``` STATED COVERAGE: [PERCENTAGE, if known] Identify what is untested regardless of coverage numbers: 1. UNTESTED BRANCHES - conditions where only one side is exercised 2. UNTESTED ERROR PATHS - every throw, error return and catch block with no test 3. UNTESTED COMBINATIONS - where two parameters interact and only individual values are tested 4. ASSERTED-BUT-NOT-VERIFIED - tests that call the code and assert something trivially true. These inflate coverage and verify nothing. Quote them. 5. MOCKED-AWAY BEHAVIOUR - where a mock is so complete that the test verifies the mock rather than the code. Flag any test where changing the real implementation would not fail the test. 6. STATE AND SEQUENCE - ordering, repeated calls, calls after failure, concurrent calls 7. UNTESTED SIDE EFFECTS - things the code writes, sends or mutates that no test checks 8. THE SPEC GAP - behaviour described in comments, docstrings or names that no test enforces For each gap: the risk (what bug could ship undetected), and the one test that would close it. Then rank all gaps by risk and give the five tests to write first. Also: name any existing test that should be deleted - tests that are redundant, test the framework, or assert implementation details that make refactoring harder.
What you get: Risk-ranked coverage gaps including fake-passing tests and over-mocked tests, plus a delete list.
Tip: Point 5 catches the most common testing failure: a test so thoroughly mocked that it would pass even if you deleted the function body.
Write test cases from a requirement before any code exists
Turn a spec into test cases you can code against.
Turn this requirement into test cases. No implementation exists yet. REQUIREMENT: """ [PASTE] """ CONTEXT: [WHERE THIS FITS, WHO USES IT] TEST FRAMEWORK: [DETAIL] Produce: 1. AMBIGUITIES - every place this requirement does not specify behaviour. For each, the question to ask and the two or more reasonable interpretations. Do this FIRST and do not resolve them yourself. 2. ACCEPTANCE CRITERIA - the requirement restated as Given/When/Then, one per distinct behaviour. 3. TEST CASE TABLE - ID | Scenario | Input | Expected | Type (happy / boundary / error / edge). Aim for breadth over depth. 4. THE CASES THE REQUIREMENT DOES NOT ANSWER - listed separately as [UNDEFINED: what should happen when...]. These are the ones to take back before building. 5. NON-FUNCTIONAL QUESTIONS the requirement omits: performance at what scale, concurrent access, what happens on partial failure, idempotency, audit or logging needs, permissions. 6. THE TEST CODE for the unambiguous cases, in my framework, written to fail against a non-existent implementation. Rules: - Do not invent behaviour for an ambiguous case. Flag it. - Section 1 and 4 are the most valuable output. Requirements are almost always underspecified, and finding it now is cheap.
What you get: An ambiguity list, Given/When/Then criteria, a case table, and failing test code for everything unambiguous.
Tip: Running this before writing code turns a vague ticket into a specific one. The ambiguity list is usually longer than the requirement.
Fix a flaky test properly
Diagnose and repair a test that fails intermittently.
This test is flaky. Diagnose it and fix it properly - do not just add a retry or increase a timeout. TEST: ``` [PASTE] ``` CODE UNDER TEST: ``` [PASTE] ``` FAILURE RATE: [HOW OFTEN] FAILURE MESSAGE WHEN IT FAILS: ``` [PASTE] ``` TEST RUNNER: [FRAMEWORK, PARALLEL OR SERIAL, CI ENVIRONMENT] Diagnose against the standard flakiness causes: 1. TIMING - fixed sleeps, tight timeouts, assuming an async operation has completed, polling without a proper wait condition 2. ORDER DEPENDENCE - relies on state from another test, or on running first/last 3. SHARED STATE - database rows, files, module-level variables, singletons, caches not reset between tests 4. NON-DETERMINISM - current time, random values, UUIDs, map/dict iteration order, unordered query results, floating-point comparison 5. PARALLELISM - port collisions, same fixture data, shared temp files, database contention 6. EXTERNAL DEPENDENCIES - real network calls, third-party services, DNS 7. RESOURCE LIMITS - slower CI machines exposing a timing assumption For each: does it apply here? Quote the line that makes you think so. Then: - THE DIAGNOSIS - the most likely cause, with the specific line responsible - THE PROPER FIX - rewritten test code that removes the non-determinism at its source - WHY NOT A RETRY - what the retry would be hiding, and whether that thing could also affect production - IS THE TEST WRONG OR THE CODE - sometimes a flaky test is correctly detecting a real race in the code. Say which this is. If it is the code, the test is doing its job and the fix belongs in the implementation.
What you get: A cause diagnosis with the responsible line quoted, a deterministic rewrite, and a judgement on whether the code is the real problem.
Tip: The last question is the one people skip. A meaningful fraction of flaky tests are correctly reporting a real race, and retrying them ships the bug.
Design integration tests for an API
Test an endpoint the way a real client would use it.
Design integration tests for this API. API SPECIFICATION OR CODE: ``` [PASTE - OPENAPI SPEC, ROUTE HANDLERS, OR DESCRIPTION] ``` STACK: [FRAMEWORK, LANGUAGE, DATABASE] AUTH MODEL: [HOW AUTHENTICATION AND AUTHORISATION WORK] TEST INFRASTRUCTURE AVAILABLE: [real database / testcontainers / in-memory / mocked] Produce a test plan covering: 1. CONTRACT - for each endpoint: status codes, response shape, required and optional fields, content types. Test that the documented contract holds. 2. VALIDATION - missing required fields, wrong types, out-of-range values, extra unexpected fields, malformed JSON, oversized payloads. 3. AUTHORISATION - for each endpoint: unauthenticated, authenticated but not permitted, permitted, and the IDOR case (authenticated user requesting another user's resource by ID). This last one is the most commonly missing test in any API suite. 4. STATE TRANSITIONS - operations performed in the wrong order, on already-deleted resources, twice (idempotency), concurrently. 5. PERSISTENCE - that writes actually persisted, that reads reflect writes, that failed requests left no partial state. 6. ERROR RESPONSES - correct status codes, no internal detail or stack traces leaked, consistent error shape. 7. PAGINATION AND FILTERING - if present: first page, last page, beyond last, invalid cursor, limit boundaries. For each: the test name, the setup required, the request, and the assertion. Then: the test code for the top ten, in my stack. Also state what should NOT be an integration test here - what belongs in a unit test instead, and why.
What you get: A structured integration test plan with the IDOR case included, plus code for the top ten tests.
Tip: Point 3's IDOR case is missing from most API test suites and is one of the most commonly exploited API flaws.
Refactor a test suite that is slow or unmaintainable
Fix a test suite people have started ignoring.
Review this test suite for maintainability and speed. TESTS: ``` [PASTE REPRESENTATIVE TESTS] ``` SUITE RUNTIME: [DURATION] TEST COUNT: [NUMBER] SLOWEST TESTS: [IF KNOWN] MAIN COMPLAINT: [too slow / too brittle / hard to understand / people skip it] Produce: 1. SPEED - what is making it slow. Check for: real database or network per test, no shared setup, container startup per test, unnecessary sleeps, fixtures rebuilt repeatedly, everything an integration test that could be a unit test. Estimate the saving from each fix. 2. BRITTLENESS - tests that fail when behaviour has not changed. Look for: asserting on implementation details, over-specified assertions (exact whitespace, full object equality where one field matters), hardcoded IDs or dates, dependence on ordering, over-mocking. 3. READABILITY - tests where you cannot tell from the name and body what is being verified. Give the rewrite. 4. DUPLICATION - near-identical tests that should be a parameterised/table-driven test. Show the consolidation. 5. THE TEST PYRAMID - given what these tests cover, what proportion is unit / integration / end-to-end, and what it should be. Name specific tests that are at the wrong level. 6. DELETE LIST - tests to remove: redundant, testing the framework, or asserting things that will never break. Deleting tests is a legitimate improvement; justify each. 7. THE PLAN - ordered by (impact / effort), what to do first. Be concrete. Quote the tests you are talking about.
What you get: A diagnosis of speed and brittleness with quoted examples, a consolidation plan and a justified delete list.
Tip: Permission to delete tests is the unlock. A suite nobody trusts is worse than a smaller suite everyone does.
Generate property-based test properties
Move from example tests to properties that hold for all inputs.
Identify property-based tests for this code. LANGUAGE AND PROPERTY TESTING LIBRARY: [e.g. Hypothesis, fast-check, QuickCheck, jqwik, proptest] CODE: ``` [PASTE] ``` WHAT IT IS SUPPOSED TO DO: [SPECIFICATION] Identify properties from the standard families, keeping only those that genuinely apply: 1. ROUND TRIP - encode then decode returns the original (serialisation, parsing, compression, encryption) 2. INVARIANT - something true of every output regardless of input (length, sortedness, sum preserved, set membership, non-negative) 3. IDEMPOTENCE - applying twice equals applying once (normalising, sanitising, sorting, deduplicating) 4. COMMUTATIVITY / ASSOCIATIVITY - order of operations does not matter, where it should not 5. ORACLE - agrees with a simpler, slower, obviously-correct implementation 6. METAMORPHIC - a known change to the input produces a known change to the output (adding an element increases count by one; scaling all inputs scales the result) 7. NEVER CRASHES - for all valid inputs in the domain, no unexpected exception For each applicable property: - State it precisely - The input generator, including how to constrain it to valid inputs - The assertion - Which real bug class it would catch Then write the test code. Also: name the properties you considered and rejected, and why they do not hold here. A property that is almost true is a common source of false failures - floating point, unicode normalisation and timezone handling are the usual culprits. Flag any of those that apply.
What you get: Applicable properties with generators and assertions, plus explicitly rejected properties and near-miss warnings.
Tip: The rejected list is worth reading. 'Sorting is idempotent' holds; 'trimming whitespace is idempotent' fails on some unicode, and that distinction is where property tests earn their keep.
Where AI actually helps here
- Enumerating edge cases you would not have listed
- Writing the tedious fixtures and setup
- Converting an acceptance criteria list into test names
Where it falls down
- Deciding what is worth testing. It will test the getters
- Testing behaviour it cannot see — anything crossing a boundary into code you did not paste
- Mocking sensibly. It over-mocks until the test proves nothing
The mistake almost everyone makes: Giving it the implementation
Give it the spec, not the code. Write tests against these acceptance criteria. Do not look at how it is implemented. If you have no spec, write the spec first — that is a separate prompt and it is the more valuable one. Tests derived from an implementation pass on day one and catch nothing after.
Free tool: Code Review Prompt Builder
Runs in your browser. No sign-up, nothing uploaded.
Questions people ask
Can AI write good unit tests?
Good structure, yes. Good judgement about coverage, no. It will happily produce forty tests where six would do, and miss the one integration point that actually breaks.
How do I get AI to test edge cases?
Name the categories: empty, null, zero, negative, maximum, unicode, duplicate, out of order, concurrent. Left to itself it tests the happy path and one obvious failure.