Aller au contenu principal

Test de code avec Cursor : Unités, Intégration et E2E

Cursor Testing

Les tests sont la pierre angulaire du développement logiciel fiable. Les capacités IA de Cursor peuvent vous aider à écrire des suites de tests complètes, identifier les cas limites et maintenir la couverture de test. Ce guide couvre les workflows de tests unitaires, d'intégration et de bout en bout.

Prérequis​

Avant de commencer :

  • Cursor installé (dernière version recommandée)
  • Une base de code avec un framework de test configuré
  • Familiarité avec les concepts de test (assertions, mocks, fixtures)

Configuration des tests dans Cursor​

Structure du projet​

project/
src/
components/
utils/
api/
tests/
unit/ # Tests unitaires
integration/ # Tests d'intégration
e2e/ # Tests de bout en bout
jest.config.js
playwright.config.ts
vitest.config.ts

Configuration de Jest avec TypeScript​

// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|less|scss)$': 'identity-obj-proxy',
},
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/**/index.ts',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};

Écriture de tests unitaires avec l'IA​

Génération de tests à partir du code​

Sélectionnez un snippet de code et utilisez la commande Ctrl+L de Cursor :

Écris des tests unitaires complets pour cette fonction :
- Cas nominaux
- Cas limites (entrée vide, très grande valeur, caractères spéciaux)
- Gestion des erreurs
- Conditions aux limites

Exemple : tester des fonctions utilitaires​

// src/utils/validation.ts
export const isValidEmail = (email: string): boolean => {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
};

export const formatDate = (
date: Date,
format: string = 'YYYY-MM-DD'
): string => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');

return format
.replace('YYYY', String(year))
.replace('MM', month)
.replace('DD', day);
};

Cursor génère les tests :

// tests/unit/validation.test.ts
import { isValidEmail, formatDate } from '@/utils/validation';

describe('isValidEmail', () => {
it('returns true for valid email', () => {
expect(isValidEmail('[email protected]')).toBe(true);
expect(isValidEmail('[email protected]')).toBe(true);
});

it('returns false for invalid email', () => {
expect(isValidEmail('')).toBe(false);
expect(isValidEmail('not-an-email')).toBe(false);
expect(isValidEmail('@domain.com')).toBe(false);
expect(isValidEmail('user@')).toBe(false);
expect(isValidEmail('user@domain')).toBe(false);
});

it('handles edge cases', () => {
expect(isValidEmail('[email protected]')).toBe(true);
expect(isValidEmail('[email protected]')).toBe(true);
});
});

describe('formatDate', () => {
it('formats date with default format', () => {
const date = new Date('2024-03-15');
expect(formatDate(date)).toBe('2024-03-15');
});

it('formats date with custom format', () => {
const date = new Date('2024-03-15');
expect(formatDate(date, 'DD/MM/YYYY')).toBe('15/03/2024');
expect(formatDate(date, 'MM-DD-YYYY')).toBe('03-15-2024');
});

it('handles edge cases', () => {
const janFirst = new Date('2024-01-01');
expect(formatDate(janFirst)).toBe('2024-01-01');

const dec31 = new Date('2024-12-31');
expect(formatDate(dec31)).toBe('2024-12-31');
});
});

Tester des composants React​

// tests/unit/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from '@/components/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();
});

it('shows loading spinner when isLoading is true', () => {
render(<Button isLoading>Loading</Button>);
expect(screen.getByTestId('spinner')).toBeInTheDocument();
});
});

Tests d'intégration​

Tester des endpoints API​

// tests/integration/api.test.ts
import request from 'supertest';
import { app } from '@/app';
import { prisma } from '@/db';

describe('API Integration Tests', () => {
beforeEach(async () => {
await prisma.user.deleteMany();
});

afterAll(async () => {
await prisma.$disconnect();
});

describe('POST /api/users', () => {
it('creates a new user', async () => {
const response = await request(app)
.post('/api/users')
.send({
name: 'Test User',
password: 'password123',
})
.expect(201);

expect(response.body.data.email).toBe('[email protected]');
});

it('returns 400 for invalid data', async () => {
await request(app)
.post('/api/users')
.send({
email: 'invalid',
name: '',
})
.expect(400);
});
});
});

Tester les interactions avec la base de données​

// tests/integration/database.test.ts
import { UserRepository } from '@/repositories/UserRepository';
import { prisma } from '@/db';

describe('UserRepository', () => {
let repository: UserRepository;

beforeEach(() => {
repository = new UserRepository();
});

afterEach(async () => {
await prisma.user.deleteMany();
});

it('creates and retrieves a user', async () => {
const user = await repository.create({
name: 'Test User',
});

expect(user.id).toBeDefined();

const found = await repository.findById(user.id);
expect(found?.email).toBe('[email protected]');
});
});

Tests de bout en bout avec Playwright​

Configuration de Playwright​

npm init playwright@latest

Génération de tests E2E​

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

test.describe('Authentication Flow', () => {
test('user can log in', async ({ page }) => {
await page.goto('/login');

await page.fill('[data-testid="email-input"]', '[email protected]');
await page.fill('[data-testid="password-input"]', 'password123');
await page.click('[data-testid="login-button"]');

await expect(page).toHaveURL('/dashboard');
await expect(page.locator('[data-testid="welcome-message"]')).toContainText(
'Welcome'
);
});

test('shows error for invalid credentials', async ({ page }) => {
await page.goto('/login');

await page.fill('[data-testid="email-input"]', '[email protected]');
await page.fill('[data-testid="password-input"]', 'wrongpassword');
await page.click('[data-testid="login-button"]');

await expect(page.locator('[data-testid="error-message"]')).toContainText(
'Invalid credentials'
);
});
});

Pattern Page Object avec Cursor​

// tests/e2e/pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';

export class LoginPage {
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly loginButton: Locator;
readonly errorMessage: Locator;

constructor(private page: Page) {
this.emailInput = page.locator('[data-testid="email-input"]');
this.passwordInput = page.locator('[data-testid="password-input"]');
this.loginButton = page.locator('[data-testid="login-button"]');
this.errorMessage = page.locator('[data-testid="error-message"]');
}

async goto() {
await this.page.goto('/login');
}

async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
}

Développement piloté par les tests avec Cursor​

Cycle rouge-vert-refactor​

  1. Rouge : Écrivez un test décrivant le comportement attendu
  2. Vert : Utilisez Cursor pour générer le code d'implémentation
  3. Refactor : Demandez à Cursor de nettoyer le code tout en gardant les tests passants

Exemple : TDD avec Cursor​

// Étape 1 : Écrivez le test
// tests/unit/calculator.test.ts
import { Calculator } from '@/utils/Calculator';

describe('Calculator', () => {
it('adds two numbers', () => {
const calc = new Calculator();
expect(calc.add(2, 3)).toBe(5);
});

it('handles negative numbers', () => {
const calc = new Calculator();
expect(calc.add(-2, 3)).toBe(1);
});
});
// Étape 2 : Cursor génère l'implémentation
// src/utils/Calculator.ts
export class Calculator {
add(a: number, b: number): number {
return a + b;
}
}

Mocks et stubs​

Mocks Jest​

// tests/unit/api.test.ts
import { fetchUserData } from '@/api/users';
import { getDatabaseConnection } from '@/db';

jest.mock('@/db', () => ({
getDatabaseConnection: jest.fn(),
}));

describe('fetchUserData', () => {
it('returns user data from database', async () => {
const mockQuery = jest.fn().mockResolvedValue({
rows: [{ id: 1, name: 'Test User' }],
});

(getDatabaseConnection as jest.Mock).mockReturnValue({
query: mockQuery,
});

const result = await fetchUserData(1);
expect(result).toEqual({ id: 1, name: 'Test User' });
expect(mockQuery).toHaveBeenCalledWith(
'SELECT * FROM users WHERE id = $1',
[1]
);
});
});

MSW pour le mocking d'API​

// tests/mocks/handlers.ts
import { rest } from 'msw';

export const handlers = [
rest.get('/api/users', (req, res, ctx) => {
return res(
ctx.json({
users: [
{ id: 1, name: 'Test User' },
{ id: 2, name: 'Another User' },
],
})
);
}),

rest.post('/api/users', async (req, res, ctx) => {
const body = await req.json();
return res(
ctx.status(201),
ctx.json({ id: 3, ...body })
);
}),
];

Couverture de test et qualité​

Générer des rapports de couverture​

# Jest
npx jest --coverage

# Vitest
npx vitest --coverage

# Voir dans Cursor
# Cliquez sur le lien du rapport HTML dans le terminal

Analyse de couverture par Cursor​

Analyse cette suite de tests :
1. Quels cas limites sont couverts par les tests ?
2. Quelles lacunes de test existent ?
3. Comment améliorer la couverture de branches ?

Configuration de l'intégration continue​

GitHub Actions​

# .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 run test:unit
- run: npm run test:integration
- run: npm run test:e2e

Référence rapide des tests​

FrameworkUsagePrompt Cursor
JestUnités/Intégration"Écris un test Jest pour ce composant"
VitestTests unitaires rapides"Génère des tests Vitest couvrant les cas limites"
PlaywrightNavigateur E2E"Crée un test Playwright pour le flux de connexion"
CypressNavigateur E2E"Écris un test Cypress pour la soumission de formulaire"
MSWMocking d'API"Configure les handlers MSW pour l'API utilisateur"
React Testing LibraryTests de composants"Teste ce composant React avec RTL"

Patterns de test courants​

PatternExemple
Fonctions factorycreateUser({ role: 'admin' })
Setup/TeardownbeforeEach / afterEach
Tests paramétrésit.each([[1, 2, 3], [4, 5, 9]])
Tests snapshotexpect(tree).toMatchSnapshot()
Spiesjest.spyOn(console, 'error')

Ressources connexes​

Dernière mise à jour : Juillet 2025