Skip to main content

Test-Driven Development with Cursor: Unit, Integration, and E2E

Cursor Testing Guide

Testing is essential for maintaining code quality and confidence in deployments. Cursor's AI capabilities make test-driven development (TDD) more accessible by generating tests, suggesting edge cases, and helping you achieve better coverage. This guide covers unit, integration, and end-to-end testing workflows.

Prerequisites​

Before starting:

  • Cursor installed (latest version)
  • Node.js installed
  • Basic understanding of testing concepts

Unit Testing with Jest​

Setting Up Jest​

Install Jest and related packages:

npm install --save-dev jest @types/jest ts-jest

Configure jest.config.js:

module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.test.ts'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/**/*.test.ts',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};

Generating Unit Tests with Cursor​

Select a function and ask Cursor to generate tests:

// src/utils/calculator.ts
export const calculateDiscount = (
price: number,
discountPercent: number,
maxDiscount?: number
): number => {
if (price < 0 || discountPercent < 0 || discountPercent > 100) {
throw new Error('Invalid input');
}

const discount = (price * discountPercent) / 100;
const finalDiscount = maxDiscount ? Math.min(discount, maxDiscount) : discount;

return Number((price - finalDiscount).toFixed(2));
};

Prompt:

Write comprehensive Jest unit tests for this function, including:
- Happy path tests
- Edge cases (zero, negative, boundary values)
- Error cases
- Tests for the maxDiscount parameter

Cursor generates:

// src/utils/__tests__/calculator.test.ts
import { calculateDiscount } from '../calculator';

describe('calculateDiscount', () => {
describe('happy path', () => {
it('should calculate 10% discount correctly', () => {
expect(calculateDiscount(100, 10)).toBe(90);
});

it('should calculate 50% discount correctly', () => {
expect(calculateDiscount(200, 50)).toBe(100);
});
});

describe('maxDiscount parameter', () => {
it('should apply maxDiscount when discount exceeds it', () => {
expect(calculateDiscount(1000, 50, 100)).toBe(900);
});

it('should ignore maxDiscount when discount is smaller', () => {
expect(calculateDiscount(100, 10, 50)).toBe(90);
});
});

describe('edge cases', () => {
it('should return original price with 0% discount', () => {
expect(calculateDiscount(100, 0)).toBe(100);
});

it('should return 0 with 100% discount', () => {
expect(calculateDiscount(100, 100)).toBe(0);
});

it('should handle decimal prices', () => {
expect(calculateDiscount(99.99, 10)).toBe(89.99);
});
});

describe('error cases', () => {
it('should throw error for negative price', () => {
expect(() => calculateDiscount(-10, 10)).toThrow('Invalid input');
});

it('should throw error for negative discount', () => {
expect(() => calculateDiscount(100, -5)).toThrow('Invalid input');
});

it('should throw error for discount over 100%', () => {
expect(() => calculateDiscount(100, 101)).toThrow('Invalid input');
});
});
});

Testing React Components​

// src/components/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
it('renders with correct text', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});

it('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});

it('is disabled when loading', () => {
render(<Button isLoading>Loading</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
});

Vitest for Modern Projects​

Vitest Configuration​

// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
thresholds: {
lines: 80,
functions: 80,
branches: 80,
statements: 80,
},
},
},
});

Vitest Test Example​

// src/utils/formatDate.test.ts
import { describe, it, expect } from 'vitest';
import { formatDate } from './formatDate';

describe('formatDate', () => {
it('formats ISO date to readable string', () => {
const result = formatDate('2024-01-15');
expect(result).toBe('January 15, 2024');
});

it('handles invalid dates gracefully', () => {
expect(() => formatDate('invalid')).toThrow();
});
});
Vitest vs Jest

Vitest offers native TypeScript support, faster execution, and better ESM compatibility. Use it for new projects; stick with Jest if you have an existing test suite.

Integration Testing​

API Integration Tests​

// tests/integration/users.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createApp } from '../../src/app';
import { setupTestDB, teardownTestDB } from '../helpers/database';

const app = createApp();

describe('Users API Integration', () => {
beforeAll(async () => {
await setupTestDB();
});

afterAll(async () => {
await teardownTestDB();
});

describe('POST /api/users', () => {
it('creates a user and persists to database', async () => {
const response = await app.request('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Integration Test',
password: 'password123',
}),
});

expect(response.status).toBe(201);
const body = await response.json();
expect(body.data.email).toBe('[email protected]');
expect(body.data.id).toBeDefined();
});
});
});

Database Integration Setup​

// tests/helpers/database.ts
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export async function setupTestDB() {
// Run migrations
// Seed test data
}

export async function teardownTestDB() {
// Clean up test data
await prisma.$executeRaw`TRUNCATE TABLE users CASCADE`;
await prisma.$disconnect();
}

End-to-End Testing​

Cypress Setup​

npm install --save-dev cypress
npx cypress open

Writing E2E Tests with Cursor​

// cypress/e2e/auth.cy.js
describe('Authentication Flow', () => {
beforeEach(() => {
cy.visit('/login');
});

it('allows user to log in with valid credentials', () => {
cy.get('[data-testid="email-input"]').type('[email protected]');
cy.get('[data-testid="password-input"]').type('password123');
cy.get('[data-testid="login-button"]').click();

cy.url().should('include', '/dashboard');
cy.get('[data-testid="welcome-message"]').should('contain', 'Welcome');
});

it('shows error for invalid credentials', () => {
cy.get('[data-testid="email-input"]').type('[email protected]');
cy.get('[data-testid="password-input"]').type('wrongpassword');
cy.get('[data-testid="login-button"]').click();

cy.get('[data-testid="error-message"]')
.should('be.visible')
.and('contain', 'Invalid credentials');
});
});

Playwright Alternative​

// tests/e2e/checkout.spec.ts
import { test, expect } from '@playwright/test';

test('user can complete checkout', async ({ page }) => {
await page.goto('/products');

// Add item to cart
await page.click('[data-testid="add-to-cart-1"]');
await page.click('[data-testid="cart-icon"]');

// Proceed to checkout
await page.click('[data-testid="checkout-button"]');

// Fill shipping info
await page.fill('[name="address"]', '123 Test St');
await page.fill('[name="city"]', 'Test City');
await page.selectOption('[name="country"]', 'US');

// Complete order
await page.click('[data-testid="place-order"]');

await expect(page.locator('[data-testid="order-confirmation"]')).toBeVisible();
});

Test Coverage and Quality​

Coverage Reports​

Generate and view coverage:

# Jest
npm test -- --coverage

# Vitest
npx vitest --coverage

AI-Assisted Coverage Improvement​

Share your coverage report with Cursor:

My test coverage is at 65%. Here are the uncovered lines:
[paste coverage report]

Suggest tests to cover these edge cases.

Testing Best Practices​

PracticeDescription
AAA PatternArrange, Act, Assert - structure every test clearly
One assertion per testKeep tests focused and readable
Descriptive namesTest names should read like documentation
Mock external depsIsolate units by mocking databases, APIs
Test behavior, not implementationRefactor-safe tests verify outcomes
Use data-testidAdd stable selectors for E2E tests

Test Utilities and Helpers​

// src/test/factories.ts
import { faker } from '@faker-js/faker';

export const createUserFixture = (overrides = {}) => ({
id: faker.string.uuid(),
email: faker.internet.email(),
name: faker.person.fullName(),
createdAt: new Date(),
...overrides,
});

export const createProductFixture = (overrides = {}) => ({
id: faker.string.uuid(),
name: faker.commerce.productName(),
price: Number(faker.commerce.price()),
...overrides,
});

Running Tests in Cursor​

Terminal Integration​

# Run all tests
npm test

# Run with watch mode
npm test -- --watch

# Run specific file
npm test -- src/utils/calculator.test.ts

# Run with coverage
npm test -- --coverage

Test Explorer​

Cursor supports the VS Code Test Explorer:

  1. Open the Testing sidebar
  2. See all tests organized by file
  3. Click to run individual tests
  4. View pass/fail status inline

Continuous Integration​

GitHub Actions Example​

# .github/workflows/test.yml
name: Tests
on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test
- run: npm run test:e2e

Quick Reference​

TaskCursor Prompt
Generate unit tests"Write Jest tests for this function with edge cases"
Mock external API"Create a mock for this API client using MSW"
Fix failing test"This test is failing with [error]. Fix it."
Improve coverage"Suggest tests to improve coverage for this file"
E2E test flow"Write a Cypress test for the user checkout flow"

Last updated: July 2025