Skip to main content

cursor-ai-testing-patterns

Effective test automation patterns with Cursor AI IDE including Composer for test suite generation, Cmd+K for inline test edits, Chat for test debugging, codebase-aware test generation, and rules configuration for testing conventions.

Source facts

Repository
PramodDutta/qaskills
Last source activity
April 14, 2026 at 07:59
Detected SKILL.md language
English
Stars
232
Forks
29

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
Cursor AI Testing Patterns
description
Effective test automation patterns with Cursor AI IDE including Composer for test suite generation, Cmd+K for inline test edits, Chat for test debugging, codebase-aware test generation, and rules configuration for testing conventions.
version
1.0.0
author
thetestingacademy
license
MIT
tags
["cursor","cursor-ai","ai-ide","composer","test-generation","inline-editing","chat-debugging","codebase-aware","rules-for-ai","testing-patterns"]
testingTypes
["unit","integration","e2e","api"]
frameworks
["vitest","jest","playwright","cypress","pytest"]
languages
["typescript","javascript","python"]
domains
["web","backend","api","frontend"]
agents
["claude-code","cursor","github-copilot","windsurf","codex","aider","continue","cline","zed","bolt","gemini-cli","amp"]
# Cursor AI Testing Patterns Skill You are an expert in using Cursor AI IDE for test automation. When the user asks you to generate tests with Cursor, configure Cursor rules for testing, use Composer for multi-file test generation, or optimize Cursor workflows for QA, follow these detailed instructions. ## Core Principles 1. **Codebase-aware generation** -- Cursor indexes your entire codebase. Reference files, functions, and patterns using @mentions to give the AI full context for test generation. 2. **Rules for consistent quality** -- Configure .cursorrules to enforce testing conventions, naming patterns, and framework preferences across all AI-generated tests. 3. **Composer for multi-file operations** -- Use Cursor Composer for generating test suites that span multiple files: page objects, fixtures, helpers, and test specs. 4. **Cmd+K for surgical edits** -- Use inline editing (Cmd+K) for targeted changes: adding a test case, fixing an assertion, or refactoring a single test function. 5. **Chat for investigation** -- Use Cursor Chat with @codebase to understand existing test patterns before generating new ones. Ask it to explain failures. 6. **Apply from Chat** -- When Chat suggests code, use the Apply button to directly insert or replace code in your editor rather than copy-pasting. 7. **Iterative test development** -- Generate a basic test suite first, run it, then use Cursor to fix failures and add edge cases based on execution results. ## Project Structure ``` project/ .cursorrules # Testing conventions .cursor/ rules/ testing.mdc # Testing-specific rules src/ services/ user-service.ts user-service.test.ts components/ LoginForm.tsx LoginForm.test.tsx tests/ e2e/ login.spec.ts checkout.spec.ts fixtures/ test-data.ts helpers/ test-utils.ts ``` ## Cursor Rules for Testing ```markdown <!-- .cursorrules --> # Testing Rules ## Test Framework - Use vitest for all unit and integration tests - Use Playwright for E2E tests - TypeScript strict mode in all test files ## Test Conventions - Follow Arrange-Act-Assert (AAA) pattern - Name tests: "should [expected behavior] when [condition]" - Group related tests in describe blocks - One assertion focus per test (multiple asserts allowed if testing same behavior) - Always include edge cases: null, undefined, empty values, boundary conditions - Always include error/failure test cases ## Mocking - Use vi.mock() for module mocking - Use vi.fn() for function mocking - Use vi.spyOn() for spying on existing methods - Reset mocks in beforeEach with vi.clearAllMocks() - Never mock the module under test ## Assertions - Prefer specific assertions: toBe, toEqual, toContain over toBeTruthy - Use toThrow for error testing - Use resolves/rejects for async assertions - Include meaningful error messages in assertions ## File Organization - Colocate tests with source files: foo.ts -> foo.test.ts - E2E tests in tests/e2e/ - Shared test utilities in tests/helpers/ - Test fixtures in tests/fixtures/ ## Do Not - Use any type in test files - Use setTimeout for async waiting - Write tests that depend on execution order - Hard-code API URLs or credentials - Skip tests without a TODO comment explaining why ``` ## Composer Workflows for Test Generation ```typescript // Workflow 1: Generate comprehensive test suite with Composer // Prompt: "Generate a complete test suite for @user-service.ts with vitest. // Include unit tests for all public methods, mock the database dependency, // test both success and error paths, and follow the patterns in @user-service.test.ts" // Result: Cursor generates the full test file considering both files import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { UserService } from './user-service'; // Cursor reads the actual implementation to create accurate mocks vi.mock('../db/connection', () => ({ db: { select: vi.fn(), insert: vi.fn(), update: vi.fn(), delete: vi.fn(), }, })); import { db } from '../db/connection'; describe('UserService', () => { let service: UserService; beforeEach(() => { service = new UserService(); vi.clearAllMocks(); }); // Cursor generates tests for each public method found in the source describe('findById', () => { it('should return user when found', async () => { const mockUser = { id: '1', name: 'Alice', email: 'alice@test.com' }; vi.mocked(db.select).mockResolvedValueOnce([mockUser]); const result = await service.findById('1'); expect(result).toEqual(mockUser); }); it('should return null when user not found', async () => { vi.mocked(db.select).mockResolvedValueOnce([]); const result = await service.findById('nonexistent'); expect(result).toBeNull(); }); it('should throw when database error occurs', async () => { vi.mocked(db.select).mockRejectedValueOnce(new Error('DB connection failed')); await expect(service.findById('1')).rejects.toThrow('DB connection failed'); }); }); // ... Cursor continues for all public methods }); ``` ## Cmd+K Inline Editing Patterns ```typescript // Pattern 1: Add a test case inline // Select empty space inside describe block, press Cmd+K // Prompt: "Add a test case that verifies createUser rejects duplicate emails" // Cursor generates and inserts: it('should reject duplicate emails', async () => { vi.mocked(db.insert).mockRejectedValueOnce( new Error('UNIQUE constraint failed: users.email') ); await expect( service.createUser({ name: 'Bob', email: 'existing@test.com' }) ).rejects.toThrow('Email already exists'); }); // Pattern 2: Strengthen an assertion // Select a weak assertion, press Cmd+K // Prompt: "Make this assertion more specific" // Before: expect(result).toBeTruthy(); // After: expect(result).toEqual({ id: expect.any(String), name: 'Alice', email: 'alice@test.com', createdAt: expect.any(Date), }); // Pattern 3: Convert test to parameterized // Select a test, press Cmd+K // Prompt: "Convert to parameterized test using it.each" // Before: it('should validate email format', () => { expect(validateEmail('bad')).toBe(false); }); // After: it.each([ ['bad', false], ['also-bad', false], ['missing@domain', false], ['valid@test.com', true], ['user+tag@test.com', true], ])('should validate "%s" as %s', (email, expected) => { expect(validateEmail(email)).toBe(expected); }); ``` ## Chat Debugging Patterns ```typescript // Use Cursor Chat to debug test failures // Prompt: "This test is failing with 'TypeError: Cannot read properties of undefined'. // @user-service.ts @user-service.test.ts // The findById method works in the app but the mock is not being applied. // Help me fix the mock setup." // Cursor analyzes both files and identifies the issue: // "The mock is not being hoisted correctly. vi.mock() calls are hoisted // to the top of the file, but the mock factory is using variables that // aren't defined yet. Move the mock return values to inside the factory // function or use vi.mocked() in beforeEach." // Prompt: "Generate missing test cases for @user-service.ts // by analyzing code coverage gaps. Focus on untested branches." // Cursor reads the source and identifies untested code paths: // "I found these untested branches: // 1. Line 45: else branch when user.role is 'admin' // 2. Line 67: catch block for validation errors // 3. Line 89: early return when input.email is empty // Here are tests for each..." ``` ## Multi-File Generation with Composer ```typescript // Prompt for Composer: // "Create a complete E2E test setup for the checkout flow: // 1. Page objects for CartPage, CheckoutPage, ConfirmationPage // 2. Test fixtures for test products and users // 3. E2E test spec covering add-to-cart, checkout, and confirmation // Follow patterns in @tests/e2e/ and @tests/fixtures/ // Use Playwright with TypeScript" // Cursor generates multiple files: // tests/e2e/pages/cart.page.ts import { Page, Locator } from '@playwright/test'; export class CartPage { private readonly cartItems: Locator; private readonly checkoutButton: Locator; private readonly totalPrice: Locator; constructor(private page: Page) { this.cartItems = page.getByTestId('cart-item'); this.checkoutButton = page.getByRole('button', { name: 'Checkout' }); this.totalPrice = page.getByTestId('total-price'); } async getItemCount(): Promise<number> { return this.cartItems.count(); } async getTotalPrice(): Promise<string> { return this.totalPrice.textContent() || ''; } async proceedToCheckout(): Promise<void> { await this.checkoutButton.click(); } } // tests/e2e/checkout.spec.ts import { test, expect } from '@playwright/test'; import { CartPage } from './pages/cart.page'; test.describe('Checkout Flow', () => { test('complete purchase flow', async ({ page }) => { // Navigate to product await page.goto('/products/wireless-headphones'); // Add to cart await page.getByRole('button', { name: 'Add to Cart' }).click(); await expect(page.getByText('Added to cart')).toBeVisible(); // Go to cart const cartPage = new CartPage(page); await page.goto('/cart'); expect(await cartPage.getItemCount()).toBe(1); // Proceed to checkout await cartPage.proceedToCheckout(); await expect(page).toHaveURL(/\/checkout/); }); }); ``` ## Cursor Rules File for Testing (.cursor/rules/testing.mdc) ```markdown --- description: Testing rules for AI-generated test code globs: ["**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts"] --- # Test Generation Rules ## Framework - Use vitest for unit/integration tests - Use Playwright for E2E tests - TypeScript strict mode required ## Structure - Colocate test files with source: foo.ts -> foo.test.ts - E2E tests go in tests/e2e/ - Use describe blocks grouped by function/method name - Use nested describes for sub-scenarios ## Naming - Test names: "should [expected behavior] when [condition]" - Describe blocks: function or class name being tested - Test file names: match source file with .test.ts suffix ## Assertions - Use toBe for primitives - Use toEqual for objects/arrays - Use toContain for array membership - Use toThrow for error testing - Use toHaveBeenCalledWith for mock verification - Never use toBeTruthy for non-boolean values ## Mocking - vi.mock() at top of file for module mocks - vi.fn() for individual function mocks - vi.spyOn() for partial mocking - Always verify mocks were called correctly - Clear mocks in beforeEach: vi.clearAllMocks() ## Coverage Requirements - Functions: 80% minimum - Branches: 75% minimum - Lines: 80% minimum ## Anti-Patterns to Avoid - No any types - No setTimeout/sleep for waiting - No console.log in tests (use debug mode) - No shared mutable state between tests - No tests that depend on execution order ``` ## Advanced Composer Patterns ### Pattern: Generate Test Suite from OpenAPI Spec ```typescript // Prompt for Composer: // "I have an OpenAPI spec at @openapi.yaml. Generate a complete API test suite // that tests every endpoint defined in the spec. Include: // 1. Success path tests for each endpoint // 2. Validation error tests for required fields // 3. Authentication tests (missing token, expired token) // 4. Not found tests for invalid IDs // Follow patterns in @tests/api/existing-api.test.ts" // Cursor reads the OpenAPI spec and generates targeted tests for each endpoint import { describe, it, expect, beforeAll } from 'vitest'; const BASE_URL = process.env.API_URL || 'http://localhost:3000'; let authToken: string; beforeAll(async () => { const response = await fetch(\`\${BASE_URL}/auth/login\`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'test@test.com', password: 'test123' }), }); const data = await response.json(); authToken = data.token; }); describe('GET /api/users', () => { it('should return paginated user list', async () => { const response = await fetch(\`\${BASE_URL}/api/users?page=1&limit=10\`, { headers: { Authorization: \`Bearer \${authToken}\` }, }); expect(response.status).toBe(200); const data = await response.json(); expect(data.users).toBeInstanceOf(Array); expect(data.total).toBeGreaterThanOrEqual(0); }); it('should return 401 without auth token', async () => { const response = await fetch(\`\${BASE_URL}/api/users\`); expect(response.status).toBe(401); }); }); describe('POST /api/users', () => { it('should create user with valid data', async () => { const response = await fetch(\`\${BASE_URL}/api/users\`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: \`Bearer \${authToken}\`, }, body: JSON.stringify({ name: 'Test User',
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub