Use Cursor for test-driven development by following the TDD Super-Prompt pattern: instruct Composer to write failing tests first, commit them, then generate implementation code in a separate session. Cursor's Agent mode with YOLO enabled creates an automated test-code-iterate loop that runs tests, identifies failures, and fixes code until all tests pass.
Test-Driven Development with Cursor's Agent Mode
Test-driven development works exceptionally well with Cursor because the AI can both write tests and iterate on implementation until tests pass. The key insight is separating the two phases: first generate tests that define desired behavior, then generate implementation code constrained to pass those tests. This prevents the common AI pitfall of writing tests that simply validate whatever implementation was generated. This tutorial covers the complete TDD workflow in Cursor.
Prerequisites
- Cursor installed (Pro for Agent mode and YOLO)
- A test runner configured (Vitest, Jest, pytest, go test)
- Basic understanding of TDD red-green-refactor cycle
- YOLO mode enabled in Cursor Settings for test commands
Step-by-step guide
Define the specification as a prompt
Define the specification as a prompt
Start by writing a clear specification of what the function or module should do. Open Composer (Cmd+I) and describe the expected behavior, inputs, outputs, and edge cases. Tell Cursor explicitly to write only tests, not implementation.
1// Prompt to type in Cursor Composer (Cmd+I):2// Write ONLY test cases for a calculateShipping function.3// DO NOT write the implementation.4//5// Specification:6// - Takes: { weight: number (kg), destination: 'domestic' | 'international' }7// - Returns: { cost: number (cents), estimatedDays: number }8// - Domestic: $5 base + $1/kg, 3-5 business days9// - International: $15 base + $3/kg, 7-14 business days10// - Free shipping for domestic orders over 10kg11// - Maximum weight: 30kg (throw error if exceeded)12// - Weight must be positive (throw error if zero or negative)13//14// Write tests covering ALL these rules plus edge cases.15// Use Vitest. Run with: npx vitest run src/utils/shipping.test.tsPro tip: The phrase 'DO NOT write the implementation' is critical. Without it, Cursor will generate both tests and code together, producing tests that validate the implementation rather than the specification.
Expected result: Cursor generates a complete test file with tests for every specification rule and edge case, all initially failing.
Review and commit the test file
Review and commit the test file
Review the generated tests to ensure they cover all specification requirements. Run them to confirm they all fail (red phase). Then commit the tests to Git before writing any implementation. This creates a clear checkpoint and prevents the tests from being modified during implementation.
1// After reviewing the generated tests, run them:2// npx vitest run src/utils/shipping.test.ts3// All tests should FAIL (red phase)4//5// Then commit:6// git add src/utils/shipping.test.ts7// git commit -m "test: add shipping calculator test suite (TDD red phase)"Pro tip: Committing tests before implementation creates a Git safety net. If the implementation goes wrong, you can reset to this commit and try again with a different approach.
Expected result: All tests fail as expected and are committed to Git as a baseline.
Generate implementation in a new Composer session
Generate implementation in a new Composer session
Start a FRESH Composer session (Cmd+N then Cmd+I) to implement the function. Reference the test file with @file and instruct Cursor to write code that passes all tests. The critical instruction is: do not modify the tests.
1// Prompt to type in a NEW Cursor Composer session (Cmd+I):2// @src/utils/shipping.test.ts3// Write the implementation for calculateShipping in4// src/utils/shipping.ts that passes ALL tests in the test file.5// Requirements:6// - DO NOT modify the test file7// - Run npx vitest run src/utils/shipping.test.ts after each change8// - Keep iterating until ALL tests pass9// - If a test seems wrong, tell me — do not change itPro tip: Using a FRESH session prevents context bleed from the test-writing phase. The implementation agent should only see the tests as specifications, not the reasoning behind them.
Expected result: Cursor generates the implementation, runs tests, and iterates until all tests pass (green phase).
Enable YOLO mode for automated test loops
Enable YOLO mode for automated test loops
Enable YOLO mode in Cursor Settings to let the agent automatically run test commands without asking permission each time. Add your test runner to the allowed commands list. This creates a fast automated loop: write code, run tests, read failures, fix code, repeat.
1// In Cursor Settings → Features → YOLO Mode:2// Enable YOLO mode3// Allowed commands: vitest, jest, npm test, pytest, go test4//5// With YOLO enabled, the Composer agent will:6// 1. Write implementation code7// 2. Automatically run vitest8// 3. Read test output9// 4. Fix failures10// 5. Re-run tests11// 6. Repeat until all greenPro tip: YOLO mode combined with TDD is the most powerful Cursor workflow. The agent operates in a tight feedback loop that mirrors how experienced TDD practitioners work, but at AI speed.
Expected result: The agent runs tests automatically after each code change, iterating until all tests pass without manual intervention.
Refactor with test protection
Refactor with test protection
Once all tests pass (green phase), start the refactor phase. Open a new Chat (Cmd+L), reference both files, and ask Cursor to improve the implementation while keeping all tests passing. This is the TDD refactor step where code quality improves without changing behavior.
1// Prompt to type in Cursor Chat (Cmd+L):2// @src/utils/shipping.ts @src/utils/shipping.test.ts3// Refactor the shipping calculator for better readability:4// - Extract magic numbers into named constants5// - Improve error messages6// - Add TypeScript types for inputs and outputs7// - Simplify any complex conditional logic8// ALL tests must continue to pass after refactoring.9// Do not change the public function signature.Expected result: Cursor refactors the code for quality while maintaining all test passes.
Complete working example
1import { describe, it, expect } from 'vitest';2import { calculateShipping } from './shipping';34describe('calculateShipping', () => {5 describe('domestic shipping', () => {6 it('should calculate base rate plus per-kg cost', () => {7 const result = calculateShipping({ weight: 5, destination: 'domestic' });8 expect(result.cost).toBe(1000); // $5 base + $1*5kg = $10 = 1000 cents9 });1011 it('should estimate 3-5 business days', () => {12 const result = calculateShipping({ weight: 1, destination: 'domestic' });13 expect(result.estimatedDays).toBeGreaterThanOrEqual(3);14 expect(result.estimatedDays).toBeLessThanOrEqual(5);15 });1617 it('should be free for orders over 10kg', () => {18 const result = calculateShipping({ weight: 11, destination: 'domestic' });19 expect(result.cost).toBe(0);20 });2122 it('should not be free at exactly 10kg', () => {23 const result = calculateShipping({ weight: 10, destination: 'domestic' });24 expect(result.cost).toBeGreaterThan(0);25 });26 });2728 describe('international shipping', () => {29 it('should calculate international rate', () => {30 const result = calculateShipping({ weight: 5, destination: 'international' });31 expect(result.cost).toBe(3000); // $15 base + $3*5kg = $30 = 3000 cents32 });3334 it('should estimate 7-14 business days', () => {35 const result = calculateShipping({ weight: 1, destination: 'international' });36 expect(result.estimatedDays).toBeGreaterThanOrEqual(7);37 expect(result.estimatedDays).toBeLessThanOrEqual(14);38 });39 });4041 describe('validation', () => {42 it('should throw for weight over 30kg', () => {43 expect(() => calculateShipping({ weight: 31, destination: 'domestic' }))44 .toThrow('exceeds maximum');45 });4647 it('should throw for zero weight', () => {48 expect(() => calculateShipping({ weight: 0, destination: 'domestic' }))49 .toThrow('must be positive');50 });5152 it('should throw for negative weight', () => {53 expect(() => calculateShipping({ weight: -1, destination: 'domestic' }))54 .toThrow('must be positive');55 });56 });57});Common mistakes
Why it's a problem: Generating tests and implementation in the same Composer session
How to avoid: Always use separate Composer sessions: one for tests (red), one for implementation (green). Commit tests before starting implementation.
Why it's a problem: Not telling Cursor 'DO NOT write implementation'
How to avoid: Include 'DO NOT write the implementation' or 'Write ONLY test cases' in your test generation prompt.
Why it's a problem: Allowing Cursor to modify tests during the implementation phase
How to avoid: Add 'DO NOT modify the test file' to the implementation prompt. Commit tests to Git first so changes are visible in diff.
Best practices
- Separate test generation and implementation into distinct Composer sessions
- Commit tests to Git before starting implementation to create a safety checkpoint
- Use YOLO mode with your test runner for automated test-code-iterate loops
- Tell Cursor 'DO NOT modify the test file' during the implementation phase
- Write specifications as bullet points before generating tests for complete coverage
- Use a fresh Composer session for the refactor phase to avoid context pollution
- Start with the simplest test cases and progress to edge cases for incremental TDD
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Write a comprehensive test suite for a calculateShipping function with these rules: [paste specification]. Use Vitest with describe/it blocks. Cover happy paths, edge cases, boundary values, and error conditions. DO NOT write the implementation.
Write ONLY test cases (no implementation) for a calculateShipping function. Spec: domestic = $5 base + $1/kg (3-5 days), international = $15 base + $3/kg (7-14 days), free domestic over 10kg, max weight 30kg, positive weight required. Use Vitest. Cover all rules plus edge cases. Run with npx vitest run.
Frequently asked questions
Can Cursor be used for test-driven development?
Yes. Cursor is highly effective for TDD when you follow the two-session pattern: generate tests first in one Composer session, commit them, then generate implementation in a fresh session. The agent's ability to run tests and iterate makes the red-green-refactor cycle faster than manual TDD.
What is the TDD Super-Prompt pattern?
It is a workflow where you describe the specification, instruct Cursor to write tests first (without implementation), then in a separate session ask it to write code that passes all tests. With YOLO mode, Cursor iterates automatically until all tests pass.
Does TDD work with Cursor for any language?
Yes. The pattern works with Vitest, Jest, pytest, go test, RSpec, JUnit, or any test runner. Specify your test runner in the prompt and in .cursorrules. Enable YOLO mode for the test command.
How do I prevent Cursor from cheating on tests?
Cursor 'cheats' by modifying tests to match its implementation. Prevent this by committing tests to Git first and adding 'DO NOT modify the test file' to the implementation prompt. Review the Git diff to catch any test modifications.
Should I use Chat or Composer for TDD?
Use Composer (Cmd+I) for both phases because it can create and edit files. Agent mode with YOLO enabled handles the test-run-fix loop automatically. Chat (Cmd+L) is better for the refactor phase where you want discussion before changes.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation