Skip to content

Test-Driven Development with AI Assistance

Test-driven development with AI assistance means writing the failing test first, confirming it fails for the right reason, and only then letting the agent implement against a target it cannot fudge. The red-green-refactor loop splits into four narrow instructions — write the tests, confirm the failure, implement to pass, refactor — each a separate turn.

You ask the agent to build a rate limiter. It generates something that looks right. You deploy it. Two days later your API goes down because the rate limiter does not handle concurrent requests correctly — a race condition the agent never thought to test, because nobody ever told it what “working” actually means.

The same trap closes from the other side when the agent writes the tests and the implementation in one turn. Ask for a discount-calculation helper and you get code plus a suite that passes, and two days later finance reports that orders with expired coupons still get the discount. Tests written after the code mostly prove that the code does what the code does.

TDD flips both. Write the tests first and the agent has a precise, machine-verifiable definition of success: it runs the suite, reads the failures, and iterates until they pass, with no room to guess at what you wanted. This is the single highest-leverage technique for getting reliable output from an AI coding assistant — and the “confirm red” step is what stops the agent from writing tests that pass against nothing.

  • A repeatable red-green-refactor loop you drive with the agent instead of typing every line
  • A prompt for generating a comprehensive test spec from a requirements list, and a worked example of it filled in
  • Copy-paste prompts that pin the agent to one phase at a time (tests, then code, then refactor)
  • The Cursor / Claude Code / Codex mechanics for letting the suite run unattended between phases
  • The signs that the agent is weakening your tests to force green — and the guards that stop it

The classic cycle is red, green, refactor. With an agent each phase becomes a separate, narrow instruction, and the discipline that makes the whole thing work is a single rule: never let the same turn write both the failing test and the code that satisfies it.

  1. Write the tests (red). Specify the behavior and the edge cases, and explicitly forbid the implementation. You are describing a contract, not asking for a feature.
  2. Confirm failure (red). Have the agent run the suite and show you the failures. This proves the tests target real, unimplemented behavior — not a typo’d import that “fails” for the wrong reason.
  3. Implement to pass (green). One narrow instruction: make these tests pass, do not touch the test files. The target is unambiguous and machine-checkable.
  4. Iterate and refactor. The agent runs the suite, reads failures, and adjusts until green. Once green, ask for a refactor pass — the tests are now the safety net that makes restructuring cheap.

The critical insight underneath the mechanics: the test is your specification. A well-written test communicates intent far more precisely than any natural-language prompt, which is why the review that matters happens on the tests rather than on the implementation.

Start from the behavior you want, not the implementation. You can write the tests yourself or have the agent draft them from a requirements list, but you review and approve them before any implementation is authorized. Concrete requirements produce concrete tests; a vague prompt produces tests that assert the function exists.

That prompt is the filled-in version of the template below. Reach for the template when you are starting from a requirements doc and want the agent to enumerate the cases; reach for the phase prompts in the next section when you already know the behavior you are pinning down.

A pure function in isolation makes a tidy demo and teaches nothing. Here is the loop on a production-shaped case instead: a service method with error paths and a mocked dependency.

Phase 1 — tests only. Pin the model to writing tests and nothing else:

Phase 2 — confirm red. Do not skip this. A test file importing a module that does not exist yet should fail at resolution; a test that passes here is a test that asserts nothing.

Phase 3 — implement to green. Only now do you authorize implementation, and you fence off the tests:

That last sentence is load-bearing. Without it, an agent that gets stuck will often “fix” the failing assertion rather than the code. The same fence, written as a reusable rule block rather than tied to one service, is the prompt to keep in a snippet file:

Phase 4 — refactor under green. With the suite passing you have a contract that lets you restructure safely:

The phases are identical everywhere. What differs is how each tool runs the suite and how much of the test-fix-retest loop it will do unattended.

Use Agent mode and let it run the tests itself. In Settings -> Cursor Settings -> Agents -> Auto-Run, set Auto-Run Mode to Run in Sandbox (on macOS/Linux) so commands execute automatically inside the sandbox without prompting — this is the unattended path Cursor recommends. Then add npx vitest (or npm test) to the Command Allowlist so the test runner runs immediately even outside the sandbox. Avoid the Run Everything mode for an unattended loop: Cursor’s own security guidance says never to use it, because it skips all safety checks.

Keep the phases as separate chat turns — checkpoints let you roll back to red if the green pass goes sideways, and rewinding beats debugging forward when the agent breaks something that was passing. Watch the diff view: if a green-phase edit touches a *.test.ts file, reject it.

The most dangerous failure mode in AI-assisted TDD is the agent modifying your tests to make them pass instead of fixing the implementation. It is rarely dramatic. Watch for:

  • Assertions becoming less specific. expect(result).toBe(429) turns into expect(result).toBeDefined().
  • Tests disappearing. The agent removes a “flaky” test instead of fixing the code. A passing suite that shrank is a red flag — diff the test files before you trust a green run.
  • Mocks replacing the real behavior. The agent mocks out the exact thing the test existed to exercise.

Once the suite is green and honest, strengthening coverage is a separate pass — and the tests it adds are supposed to fail:

  • Tests pass before the code exists. If “confirm red” comes back green, the tests are not exercising the target — usually a mocked dependency returns a truthy default, or the import resolves to a stub. Never skip the confirm-red step.
  • The agent generates trivial tests. A vague prompt (“write tests for this function”) buys tests that verify the function exists and returns something. Name the behaviors, and include concrete input-output examples.
  • Tests coupled to the implementation. If tests assert on private method calls or internal data structures, the agent cannot refactor freely. Write against the public API and the expected behavior.
  • Flaky async tests get “fixed” with sleeps. When timing-dependent tests fail intermittently, agents love to paper over it with setTimeout/sleep. Prompt for deterministic control instead: “use fake timers (vi.useFakeTimers()) and advance them explicitly; do not add real delays.”
  • The suite is too slow to loop on. TDD with an agent works when tests run in seconds. Scope the runner to the file under test during the loop (npx vitest run src/services/pricing.test.ts), then run everything once at the end. Jest 30+ uses --testPathPatterns=rateLimiter (the singular --testPathPattern now warns); Vitest and Mocha take a path argument or --grep.
  • Too many tests written upfront. Start with three to five tests covering the core behavior and let the implementation reveal which edge cases matter. Thirty tests before any implementation is analysis paralysis with extra steps.