The Testing Pyramid
Unit Testing
Setting Up Unit Tests
Tsx
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { UserService } from '../services/UserService';
vi.mock('../api/userApi', () => ({
fetchUser: vi.fn(),
updateUser: vi.fn()
}));
describe('UserService', () => {
let userService: UserService;
beforeEach(() => {
userService = new UserService();
});
describe('createUser', () => {
it('should create a user with valid data', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com'
};
const result = await userService.createUser(userData);
expect(result).toMatchObject({
id: expect.any(String),
name: userData.name,
email: userData.email
});
});
});
});
Testing React Components
Tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { UserCard } from '../components/UserCard';
describe('UserCard', () => {
const mockUser = {
id: '1',
name: 'John Doe',
email: 'john@example.com',
avatar: 'https://example.com/avatar.webp'
};
it('should render user information', () => {
render(<UserCard user={mockUser} />);
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('john@example.com')).toBeInTheDocument();
});
it('should call onEdit when edit button is clicked', () => {
const mockOnEdit = vi.fn();
render(<UserCard user={mockUser} onEdit={mockOnEdit} />);
fireEvent.click(screen.getByRole('button', { name: /edit/i }));
expect(mockOnEdit).toHaveBeenCalledWith(mockUser);
});
});
Integration Testing
API Integration Tests
Tsx
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import request from 'supertest';
import app from '../app';
describe('User API Integration', () => {
beforeEach(async () => {
await setupTestDatabase();
});
afterEach(async () => {
await cleanupTestDatabase();
});
describe('POST /api/users', () => {
it('should create a new user', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
password: 'password123'
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body).toMatchObject({
id: expect.any(String),
name: userData.name,
email: userData.email
});
expect(response.body.password).toBeUndefined();
});
});
});
End-to-End Testing
Playwright E2E Tests
Tsx
import { test, expect } from '@playwright/test';
test.describe('User Management', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
await page.fill('[data-testid="email"]', 'admin@example.com');
await page.fill('[data-testid="password"]', 'password123');
await page.click('[data-testid="login-button"]');
await expect(page).toHaveURL('/dashboard');
});
test('should create a new user', async ({ page }) => {
await page.goto('/users');
await page.click('[data-testid="create-user-button"]');
await page.fill('[data-testid="user-name"]', 'John Doe');
await page.fill('[data-testid="user-email"]', 'john@example.com');
await page.fill('[data-testid="user-password"]', 'password123');
await page.click('[data-testid="submit-button"]');
await expect(page.locator('[data-testid="user-list"]')).toContainText('John Doe');
});
});
Test-Driven Development (TDD)
TDD Example
Tsx
// 1. Write a failing test
describe('UserService', () => {
describe('validateEmail', () => {
it('should return true for valid email', () => {
const userService = new UserService();
expect(userService.validateEmail('test@example.com')).toBe(true);
});
it('should return false for invalid email', () => {
const userService = new UserService();
expect(userService.validateEmail('invalid-email')).toBe(false);
});
});
});
// 2. Write minimal code to make test pass
export class UserService {
validateEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
}
// 3. Refactor if needed; 4. Repeat
Mocking and Stubbing
Tsx
import { vi } from 'vitest';
vi.mock('../services/apiService', () => ({
fetchUser: vi.fn(),
updateUser: vi.fn()
}));
vi.mock('../database/connection', () => ({
query: vi.fn()
}));
describe('UserService with mocks', () => {
it('should fetch user from API', async () => {
const mockFetchUser = vi.mocked(fetchUser);
mockFetchUser.mockResolvedValue({
id: '1',
name: 'John Doe',
email: 'john@example.com'
});
const userService = new UserService();
const user = await userService.getUser('1');
expect(mockFetchUser).toHaveBeenCalledWith('1');
expect(user).toMatchObject({
id: '1',
name: 'John Doe',
email: 'john@example.com'
});
});
});
Performance Testing
Yaml
# Load testing with Artillery
config:
target: 'http://localhost:3000'
phases:
- duration: 60
arrivalRate: 10
- duration: 120
arrivalRate: 20
scenarios:
- name: "User API Load Test"
weight: 70
flow:
- post:
url: "/api/users"
json:
name: "Test User {{ $randomString() }}"
email: "test{{ $randomString() }}@example.com"
password: "password123"