javascript-testing-patterns
Implement comprehensive testing strategies using Jest, Vitest, and Testing Library for unit tests, integration tests, and end-to-end testing with mocking, fixtures, and test-driven development. Use when writing JavaScript/TypeScript tests, setting up test infrastructure, or implementing TDD/BDD workflows.
When & Why to Use This Skill
This Claude skill provides comprehensive patterns and strategies for implementing robust testing in JavaScript and TypeScript applications. It covers everything from initial framework setup with Jest and Vitest to advanced mocking techniques, React component testing, and Test-Driven Development (TDD) workflows, ensuring high code quality and reliability through industry-standard best practices.
Use Cases
- Setting up a professional testing infrastructure for a new or existing TypeScript project using Vitest or Jest.
- Writing unit tests for complex business logic, including pure functions, class-based services, and asynchronous operations.
- Implementing advanced mocking strategies for external API calls, database dependencies, and third-party modules to ensure isolated test environments.
- Testing React UI components and custom hooks using Testing Library to verify user interactions and state changes.
- Adopting Test-Driven Development (TDD) or Behavior-Driven Development (BDD) workflows to improve software design and catch regressions early.
- Configuring automated code coverage reports and setting quality thresholds to maintain high testing standards across a codebase.
| name | javascript-testing-patterns |
|---|---|
| description | "Implement comprehensive testing strategies using Jest, Vitest, and Testing Library for unit tests, integration tests, and end-to-end testing with mocking, fixtures, and test-driven development. Use when writing JavaScript/TypeScript tests, setting up test infrastructure, or implementing TDD/BDD workflows." |
| layer | 3 |
| tech_stack | [typescript, javascript, jest, vitest] |
| topics | [jest, vitest, testing-library, mocking, tdd, react-testing] |
| depends_on | [modern-javascript-patterns] |
| complements | [e2e-testing-patterns] |
| keywords | [Jest, Vitest, describe, it, expect, mock, render, screen, fireEvent] |
JavaScript Testing Patterns
Comprehensive testing strategies for JavaScript/TypeScript applications.
Framework Setup
Jest Configuration
// jest.config.ts
import type { Config } from 'jest';
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts'],
coverageThreshold: {
global: { branches: 80, functions: 80, lines: 80, statements: 80 },
},
setupFilesAfterEnv: ['<rootDir>/src/test/setup.ts'],
};
export default config;
Vitest Configuration
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
setupFiles: ['./src/test/setup.ts'],
},
});
Unit Testing Patterns
Testing Pure Functions
import { describe, it, expect } from 'vitest';
describe('Calculator', () => {
describe('add', () => {
it('should add two positive numbers', () => {
expect(add(2, 3)).toBe(5);
});
it('should handle zero', () => {
expect(add(0, 5)).toBe(5);
});
});
describe('divide', () => {
it('should throw error when dividing by zero', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});
});
});
Testing Classes
describe('UserService', () => {
let service: UserService;
beforeEach(() => {
service = new UserService();
});
describe('create', () => {
it('should create a new user', () => {
const user = { id: '1', name: 'John', email: 'john@example.com' };
const created = service.create(user);
expect(created).toEqual(user);
expect(service.findById('1')).toEqual(user);
});
it('should throw error if user already exists', () => {
const user = { id: '1', name: 'John', email: 'john@example.com' };
service.create(user);
expect(() => service.create(user)).toThrow('User already exists');
});
});
});
Testing Async Functions
describe('ApiService', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should fetch user successfully', async () => {
const mockUser = { id: '1', name: 'John' };
global.fetch = vi.fn().mockResolvedValueOnce({
ok: true,
json: async () => mockUser,
});
const user = await service.fetchUser('1');
expect(user).toEqual(mockUser);
expect(fetch).toHaveBeenCalledWith('https://api.example.com/users/1');
});
it('should throw error if user not found', async () => {
global.fetch = vi.fn().mockResolvedValueOnce({ ok: false });
await expect(service.fetchUser('999')).rejects.toThrow('User not found');
});
});
Mocking Patterns
Mocking Modules
vi.mock('nodemailer', () => ({
default: {
createTransport: vi.fn(() => ({
sendMail: vi.fn().mockResolvedValue({ messageId: '123' }),
})),
},
}));
Dependency Injection
describe('UserService', () => {
let service: UserService;
let mockRepository: IUserRepository;
beforeEach(() => {
mockRepository = {
findById: vi.fn(),
create: vi.fn(),
};
service = new UserService(mockRepository);
});
it('should return user if found', async () => {
const mockUser = { id: '1', name: 'John' };
vi.mocked(mockRepository.findById).mockResolvedValue(mockUser);
const user = await service.getUser('1');
expect(user).toEqual(mockUser);
expect(mockRepository.findById).toHaveBeenCalledWith('1');
});
});
Spying on Functions
let loggerSpy: any;
beforeEach(() => {
loggerSpy = vi.spyOn(logger, 'info');
});
afterEach(() => {
loggerSpy.mockRestore();
});
it('should log order processing', async () => {
await service.processOrder('123');
expect(loggerSpy).toHaveBeenCalledWith('Processing order 123');
});
React Component Testing
import { render, screen, fireEvent } from '@testing-library/react';
describe('UserForm', () => {
it('should render form inputs', () => {
render(<UserForm onSubmit={vi.fn()} />);
expect(screen.getByPlaceholderText('Name')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument();
});
it('should call onSubmit with form data', () => {
const onSubmit = vi.fn();
render(<UserForm onSubmit={onSubmit} />);
fireEvent.change(screen.getByTestId('name-input'), {
target: { value: 'John Doe' },
});
fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
expect(onSubmit).toHaveBeenCalledWith({ name: 'John Doe' });
});
});
Testing Hooks
import { renderHook, act } from '@testing-library/react';
describe('useCounter', () => {
it('should increment count', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
});
Test Fixtures
import { faker } from '@faker-js/faker';
export function createUserFixture(overrides?: Partial<User>): User {
return {
id: faker.string.uuid(),
name: faker.person.fullName(),
email: faker.internet.email(),
...overrides,
};
}
Best Practices
- AAA Pattern - Arrange, Act, Assert
- One assertion per test - Or logically related assertions
- Descriptive test names - Describe what is being tested
- Use beforeEach/afterEach - For setup and teardown
- Mock external dependencies - Keep tests isolated
- Test edge cases - Not just happy paths
- Avoid implementation details - Test behavior
- Keep tests fast - Mock slow operations
- Maintain 80%+ coverage - Aim high
- Clean up after tests - Prevent pollution
Common Patterns
// Testing promises
await expect(service.fetchUser('invalid')).rejects.toThrow('Not found');
// Testing timers
vi.useFakeTimers();
vi.advanceTimersByTime(1000);
vi.useRealTimers();
// Snapshot testing
expect(container.firstChild).toMatchSnapshot();
Detailed References
For comprehensive patterns, see:
- references/integration-testing.md
- references/react-testing-patterns.md
- references/mocking-strategies.md
Resources
- Jest: https://jestjs.io/
- Vitest: https://vitest.dev/
- Testing Library: https://testing-library.com/