TL;DR
- Jest — фреймворк тестирования без конфигурации со встроенными assertions, моками и покрытием
- Matchers вроде
toBe,toEqual,toContainделают assertions читаемыми- Мокай функции с
jest.fn(), модули сjest.mock(), таймеры сjest.useFakeTimers()- Async тестирование: используй
async/await,resolves/rejectsили callbackdone- Snapshot тестирование захватывает UI — полезно для React компонентов
Подходит для: JavaScript/TypeScript разработчиков, React/Vue/Node.js проектов, команд, хотящих всё-в-одном Пропусти, если: Нужно браузерное тестирование (используй Playwright/Cypress) Время чтения: 15 минут
Твой тестовый набор выполняется 10 минут. Половина тестов flaky. Никто больше не доверяет результатам.
Jest меняет это. Он быстрый, надежный и работает из коробки. Без ада конфигурации. Без сборки пяти разных библиотек.
Этот туториал учит Jest с нуля — matchers, моки, async тестирование и паттерны, которые делают тесты поддерживаемыми.
Что такое Jest?
Jest — фреймворк тестирования JavaScript, созданный Facebook. Он запускает тесты, предоставляет assertions, мокает зависимости и генерирует отчеты о покрытии — всё в одном пакете.
Что включает Jest:
- Test runner — находит и выполняет тестовые файлы
- Библиотека assertions —
expect()со встроенными matchers - Моки — mock функции, модули, таймеры
- Покрытие — встроенные отчеты о покрытии кода
- Snapshot тестирование — захватывает и сравнивает output
- Watch режим — перезапускает тесты при изменении файлов
Установка и настройка
Новый проект
# Инициализация проекта
npm init -y
# Установка Jest
npm install --save-dev jest
# Добавление test скрипта в package.json
npm pkg set scripts.test="jest"
TypeScript проект
npm install --save-dev jest typescript ts-jest @types/jest
# Инициализация ts-jest конфига
npx ts-jest config:init
Create React App
Jest уже включен. Просто запусти:
npm test
Структура проекта
my-project/
├── src/
│ ├── calculator.js
│ └── utils/
│ └── formatters.js
├── __tests__/
│ ├── calculator.test.js
│ └── utils/
│ └── formatters.test.js
├── jest.config.js
└── package.json
Jest находит тесты в:
- Файлах, заканчивающихся на
.test.jsили.spec.js - Файлах в папках
__tests__
Написание первого теста
// src/calculator.js
function add(a, b) {
return a + b;
}
function divide(a, b) {
if (b === 0) {
throw new Error('Cannot divide by zero');
}
return a / b;
}
module.exports = { add, divide };
// __tests__/calculator.test.js
const { add, divide } = require('../src/calculator');
describe('Calculator', () => {
describe('add', () => {
test('складывает два положительных числа', () => {
expect(add(2, 3)).toBe(5);
});
test('складывает отрицательные числа', () => {
expect(add(-1, -1)).toBe(-2);
});
test('складывает с нулем', () => {
expect(add(5, 0)).toBe(5);
});
});
describe('divide', () => {
test('делит два числа', () => {
expect(divide(10, 2)).toBe(5);
});
test('выбрасывает ошибку при делении на ноль', () => {
expect(() => divide(10, 0)).toThrow('Cannot divide by zero');
});
});
});
Запуск тестов
# Запустить все тесты
npm test
# Запустить конкретный файл
npm test -- calculator.test.js
# Запустить тесты по паттерну
npm test -- --testNamePattern="складывает"
# Watch режим
npm test -- --watch
# С покрытием
npm test -- --coverage
Matchers
Matchers — методы проверки значений. Jest имеет 50+ встроенных matchers.
Частые Matchers
// Равенство
expect(2 + 2).toBe(4); // Строгое равенство (===)
expect({ a: 1 }).toEqual({ a: 1 }); // Глубокое равенство
// Истинность
expect(true).toBeTruthy();
expect(false).toBeFalsy();
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect('value').toBeDefined();
// Числа
expect(10).toBeGreaterThan(5);
expect(10).toBeGreaterThanOrEqual(10);
expect(5).toBeLessThan(10);
expect(0.1 + 0.2).toBeCloseTo(0.3); // Числа с плавающей точкой
// Строки
expect('Hello World').toMatch(/World/);
expect('Hello World').toContain('World');
// Массивы
expect([1, 2, 3]).toContain(2);
expect([1, 2, 3]).toHaveLength(3);
expect(['a', 'b']).toEqual(expect.arrayContaining(['a']));
// Объекты
expect({ a: 1, b: 2 }).toHaveProperty('a');
expect({ a: 1, b: 2 }).toHaveProperty('a', 1);
expect({ a: 1 }).toMatchObject({ a: 1 });
// Исключения
expect(() => { throw new Error('fail'); }).toThrow();
expect(() => { throw new Error('fail'); }).toThrow('fail');
expect(() => { throw new Error('fail'); }).toThrow(Error);
Отрицание Matchers
Добавь .not перед любым matcher:
expect(5).not.toBe(3);
expect([1, 2]).not.toContain(3);
expect({ a: 1 }).not.toHaveProperty('b');
Тестирование асинхронного кода
Async/Await
// src/api.js
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error('User not found');
}
return response.json();
}
module.exports = { fetchUser };
// __tests__/api.test.js
test('успешно получает пользователя', async () => {
const user = await fetchUser(1);
expect(user.name).toBe('John');
});
test('выбрасывает ошибку для невалидного пользователя', async () => {
await expect(fetchUser(999)).rejects.toThrow('User not found');
});
Промисы с resolves/rejects
test('резолвится в данные пользователя', () => {
return expect(fetchUser(1)).resolves.toMatchObject({ name: 'John' });
});
test('реджектится для отсутствующего пользователя', () => {
return expect(fetchUser(999)).rejects.toThrow();
});
Callback стиль (done)
function fetchDataWithCallback(callback) {
setTimeout(() => {
callback({ data: 'result' });
}, 100);
}
test('вызывает callback с данными', (done) => {
function callback(result) {
expect(result.data).toBe('result');
done(); // Тест ждет пока вызовется done()
}
fetchDataWithCallback(callback);
});
Моки
Моки заменяют реальные реализации контролируемыми заменителями.
Mock функции (jest.fn)
test('mock функция отслеживает вызовы', () => {
const mockCallback = jest.fn(x => x + 1);
[1, 2, 3].forEach(mockCallback);
// Проверка количества вызовов
expect(mockCallback).toHaveBeenCalledTimes(3);
// Проверка конкретных вызовов
expect(mockCallback).toHaveBeenCalledWith(1);
expect(mockCallback).toHaveBeenLastCalledWith(3);
// Проверка возвращаемых значений
expect(mockCallback.mock.results[0].value).toBe(2);
});
Mock возвращаемые значения
const mock = jest.fn();
// Возврат разных значений при последовательных вызовах
mock
.mockReturnValueOnce(10)
.mockReturnValueOnce(20)
.mockReturnValue(30);
console.log(mock()); // 10
console.log(mock()); // 20
console.log(mock()); // 30
console.log(mock()); // 30
// Mock resolved/rejected промисов
const asyncMock = jest.fn()
.mockResolvedValueOnce({ success: true })
.mockRejectedValueOnce(new Error('Failed'));
Мок модулей
// Мок всего модуля
jest.mock('./api');
const { fetchUser } = require('./api');
// Настройка mock реализации
fetchUser.mockResolvedValue({ id: 1, name: 'Mock User' });
test('использует замоканный API', async () => {
const user = await fetchUser(1);
expect(user.name).toBe('Mock User');
});
Мок с фабрикой
jest.mock('./database', () => ({
connect: jest.fn().mockResolvedValue(true),
query: jest.fn().mockResolvedValue([{ id: 1 }]),
close: jest.fn()
}));
Шпионаж за методами
const video = {
play() {
return true;
}
};
test('шпионаж за методом', () => {
const spy = jest.spyOn(video, 'play');
video.play();
expect(spy).toHaveBeenCalled();
expect(spy).toHaveReturnedWith(true);
spy.mockRestore(); // Восстановить оригинальную реализацию
});
Мок таймеров
jest.useFakeTimers();
function delayedGreeting(callback) {
setTimeout(() => callback('Hello'), 1000);
}
test('вызывает callback после задержки', () => {
const callback = jest.fn();
delayedGreeting(callback);
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(1000); // Перемотка времени
expect(callback).toHaveBeenCalledWith('Hello');
});
// Или запустить все таймеры
test('с runAllTimers', () => {
const callback = jest.fn();
delayedGreeting(callback);
jest.runAllTimers();
expect(callback).toHaveBeenCalled();
});
Setup и Teardown
describe('Database tests', () => {
let db;
// Выполняется один раз перед всеми тестами в этом describe
beforeAll(async () => {
db = await connectToDatabase();
});
// Выполняется один раз после всех тестов
afterAll(async () => {
await db.close();
});
// Выполняется перед каждым тестом
beforeEach(async () => {
await db.clear();
});
// Выполняется после каждого теста
afterEach(() => {
jest.clearAllMocks();
});
test('вставляет запись', async () => {
await db.insert({ name: 'Test' });
const records = await db.findAll();
expect(records).toHaveLength(1);
});
});
Snapshot тестирование
Snapshots захватывают output и обнаруживают непреднамеренные изменения.
// src/formatUser.js
function formatUser(user) {
return {
displayName: `${user.firstName} ${user.lastName}`,
email: user.email.toLowerCase(),
initials: `${user.firstName[0]}${user.lastName[0]}`
};
}
module.exports = { formatUser };
// __tests__/formatUser.test.js
const { formatUser } = require('../src/formatUser');
test('форматирует пользователя корректно', () => {
const user = {
firstName: 'John',
lastName: 'Doe',
email: 'John.Doe@Example.com'
};
expect(formatUser(user)).toMatchSnapshot();
});
Первый запуск создает __snapshots__/formatUser.test.js.snap:
exports[`форматирует пользователя корректно 1`] = `
{
"displayName": "John Doe",
"email": "john.doe@example.com",
"initials": "JD"
}
`;
Если output изменится, тест упадет. Обнови snapshots:
npm test -- --updateSnapshot
# или
npm test -- -u
Покрытие кода
# Генерация отчета о покрытии
npm test -- --coverage
# С конкретными порогами
npm test -- --coverage --coverageThreshold='{"global":{"branches":80,"functions":80}}'
Конфигурация покрытия
// jest.config.js
module.exports = {
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/index.js'
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
coverageReporters: ['text', 'lcov', 'html']
};
Лучшие практики
1. Один Assertion на тест (обычно)
// Плохо: несколько несвязанных assertions
test('валидация пользователя', () => {
expect(isValidEmail('test@example.com')).toBe(true);
expect(isValidEmail('invalid')).toBe(false);
expect(isValidName('John')).toBe(true);
});
// Хорошо: отдельные тесты
test('принимает валидный email', () => {
expect(isValidEmail('test@example.com')).toBe(true);
});
test('отклоняет невалидный email', () => {
expect(isValidEmail('invalid')).toBe(false);
});
2. Описательные имена тестов
// Плохо
test('test1', () => { ... });
// Хорошо
test('возвращает null когда user ID не найден', () => { ... });
3. Паттерн Arrange-Act-Assert
test('вычисляет сумму со скидкой', () => {
// Arrange
const cart = { items: [{ price: 100 }, { price: 50 }] };
const discount = 0.1;
// Act
const total = calculateTotal(cart, discount);
// Assert
expect(total).toBe(135);
});
4. Избегай тестирования деталей реализации
// Плохо: тестирует внутреннее состояние
test('устанавливает внутренний флаг', () => {
const counter = new Counter();
counter.increment();
expect(counter._count).toBe(1); // Тестирование приватного свойства
});
// Хорошо: тестирует публичное поведение
test('увеличивает счетчик', () => {
const counter = new Counter();
counter.increment();
expect(counter.getCount()).toBe(1); // Тестирование публичного метода
});
AI-Assisted Jest тестирование
AI инструменты могут ускорить написание тестов при правильном использовании.
Что AI делает хорошо:
- Генерация тест-кейсов из сигнатур функций
- Создание mock данных определенной формы
- Написание boilerplate для частых паттернов
- Предложение edge cases, которые ты мог пропустить
Что всё ещё требует людей:
- Решения о том, что стоит тестировать
- Проверка, что тесты действительно тестируют нужное
- Написание тестов для сложной бизнес-логики
- Отладка упавших тестов
FAQ
Для чего используется Jest?
Jest — фреймворк тестирования JavaScript для unit-тестов, интеграционных тестов и snapshot-тестирования. Он предоставляет test runner, библиотеку assertions, утилиты моков и покрытие кода — всё в одном пакете. Jest работает с React, Vue, Angular, Node.js и любым JavaScript или TypeScript проектом.
Jest только для React?
Нет. Хотя Jest был создан Facebook и популярен в React проектах, он работает с любой JavaScript или TypeScript кодовой базой. Jest одинаково хорошо тестирует Node.js бэкенд, Vue компоненты, Angular приложения и vanilla JavaScript. React Testing Library — отдельный пакет, который дополняет Jest для React-специфичного тестирования.
В чем разница между Jest и Mocha?
Jest — всё-в-одном фреймворк со встроенными assertions (expect), моками (jest.fn) и покрытием. Mocha — test runner, требующий отдельных библиотек: Chai для assertions, Sinon для моков, nyc для покрытия. Jest проще настроить; Mocha предлагает больше гибкости и кастомизации. Для новых проектов Jest обычно проще.
Как мокать API вызовы в Jest?
Несколько подходов работают:
- jest.mock() — мок всего fetch/axios модуля
- jest.spyOn() — шпионаж и мок конкретных методов
- Manual mocks — создание папки
__mocks__с mock реализациями - MSW (Mock Service Worker) — перехват сетевых запросов для реалистичного API мока
Официальные ресурсы
Смотрите также
- Jest & Testing Library для React - Тестирование React компонентов с Testing Library
- Mocha и Chai - Альтернативный JavaScript стек тестирования
- Пирамида автоматизации тестирования - Где unit-тесты в твоей стратегии
- Cypress Tutorial - E2E тестирование для JavaScript приложений
