Jest - Полное руководство
Что такое Jest?
Jest — это популярный фреймворк для тестирования JavaScript/TypeScript. Разработан Facebook. Работает с React, Node.js и другими проектами.
Установка
bash
npm install --save-dev jest @types/jestКонфигурация
jest.config.js
typescript
module.exports = {
testEnvironment: 'node',
testMatch: ['**/__tests__/**/*.test.ts', '**/*.test.ts'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
preset: 'ts-jest'
};package.json
json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}Написание простого теста
typescript
// sum.ts
export function sum(a: number, b: number): number {
return a + b;
}
// sum.test.ts
import { sum } from './sum';
describe('sum', () => {
test('adds 2 numbers', () => {
expect(sum(2, 3)).toBe(5);
});
test('handles negative numbers', () => {
expect(sum(-1, 1)).toBe(0);
});
});Matchers (Утверждения)
Точные значения
typescript
expect(2 + 2).toBe(4); // ===
expect({ a: 1 }).toEqual({ a: 1 }); // Глубокое сравнение
expect(true).toBeTruthy();
expect(false).toBeFalsy();
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect('text').toBeDefined();Числа
typescript
expect(4).toBeGreaterThan(3);
expect(4).toBeGreaterThanOrEqual(4);
expect(4).toBeLessThan(5);
expect(3.14).toBeCloseTo(3.1, 1); // Примерно равноСтроки и Regex
typescript
expect('team').toMatch(/^team/);
expect('testing').toMatch('test');
expect('hello').toContain('ell');Массивы
typescript
expect([1, 2, 3]).toContain(2);
expect([1, 2, 3]).toEqual(expect.arrayContaining([2]));
expect([]).toHaveLength(0);Объекты
typescript
const obj = { a: 1, b: 2 };
expect(obj).toHaveProperty('a');
expect(obj).toHaveProperty('a', 1);
expect(obj).toMatchObject({ a: 1 });Mock функции
Создание mock функции
typescript
const mockFn = jest.fn();
mockFn('hello');
mockFn({ id: 1 });
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith('hello');
expect(mockFn).toHaveBeenNthCalledWith(1, 'hello');
expect(mockFn).toHaveReturnedWith(undefined);Mock с return value
typescript
const mockFn = jest.fn()
.mockReturnValue('default')
.mockReturnValueOnce('first call')
.mockReturnValueOnce('second call');
console.log(mockFn()); // 'first call'
console.log(mockFn()); // 'second call'
console.log(mockFn()); // 'default'Mock с реализацией
typescript
const mockFn = jest.fn((x) => x * 2);
expect(mockFn(5)).toBe(10);Mock модулей
Полное мокирование модуля
typescript
// api.ts
export const fetchUser = async (id: number) => {
const res = await fetch(`/api/users/${id}`);
return res.json();
};
// __tests__/service.test.ts
jest.mock('../api', () => ({
fetchUser: jest.fn()
}));
import { fetchUser } from '../api';
test('calls API', async () => {
(fetchUser as jest.Mock).mockResolvedValue({ id: 1, name: 'John' });
const result = await fetchUser(1);
expect(result.name).toBe('John');
});Частичное мокирование модуля
typescript
jest.mock('../api', () => ({
...jest.requireActual('../api'),
fetchUser: jest.fn()
}));Snapshot Testing
typescript
test('renders correctly', () => {
const component = render(<Header title="Test" />);
expect(component).toMatchSnapshot();
});Первый запуск создает __snapshots__/Header.test.ts.snap:
exports[`renders correctly 1`] = `
<div>
<h1>Test</h1>
</div>
`;Обновить snapshots: jest -u
Async тестирование
Promise
typescript
test('fetches data', () => {
return fetchUser(1).then(data => {
expect(data.name).toBe('John');
});
});Async/Await
typescript
test('fetches data', async () => {
const data = await fetchUser(1);
expect(data.name).toBe('John');
});Callback
typescript
test('fetches data', (done) => {
fetchUser(1, (data) => {
expect(data.name).toBe('John');
done();
});
});Timers (Таймеры)
typescript
jest.useFakeTimers();
const callback = jest.fn();
setTimeout(callback, 1000);
expect(callback).not.toHaveBeenCalled();
jest.runAllTimers();
expect(callback).toHaveBeenCalled();
jest.useRealTimers();Setup и Teardown
typescript
describe('Database', () => {
beforeAll(() => {
// Один раз в начале
db.connect();
});
beforeEach(() => {
// Перед каждым тестом
db.clear();
});
afterEach(() => {
// После каждого теста
jest.clearAllMocks();
});
afterAll(() => {
// Один раз в конце
db.disconnect();
});
test('saves data', () => {
// ...
});
});Coverage (Покрытие)
bash
jest --coverageКонфигурация порогов:
typescript
// jest.config.js
module.exports = {
collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts'],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};Полезные опции
bash
jest # Запустить все тесты
jest --watch # Watch mode
jest --coverage # С покрытием
jest --updateSnapshot # Обновить snapshots
jest --testNamePattern="add" # Только тесты с "add"
jest --testPathPattern="api" # Только файлы с "api"
jest --bail # Остановить на первой ошибке
jest --verbose # Подробный выводПрактический пример
typescript
// calculator.ts
export class Calculator {
add(a: number, b: number): number {
return a + b;
}
subtract(a: number, b: number): number {
return a - b;
}
}
// calculator.test.ts
import { Calculator } from './calculator';
describe('Calculator', () => {
let calculator: Calculator;
beforeEach(() => {
calculator = new Calculator();
});
describe('add', () => {
test('adds positive numbers', () => {
expect(calculator.add(2, 3)).toBe(5);
});
test('adds negative numbers', () => {
expect(calculator.add(-1, -2)).toBe(-3);
});
test('handles zero', () => {
expect(calculator.add(0, 5)).toBe(5);
});
});
describe('subtract', () => {
test('subtracts numbers correctly', () => {
expect(calculator.subtract(5, 3)).toBe(2);
});
});
});Дальше
Изучите Vitest для более быстрого фреймворка.