Олег Марков
Тестирование с Jest - практическое руководство для JavaScript и TypeScript
Введение
Jest сегодня один из самых популярных фреймворков для тестирования JavaScript и TypeScript кода. Его используют в проектах на React, Node.js, Next.js и во множестве других экосистем. Он позволяет быстро писать понятные автотесты, запускать их параллельно, собирать покрытие и получать наглядные отчеты.
Здесь вы увидите, как на практике использовать Jest для модульного и частично интеграционного тестирования. Мы последовательно разберем установку, конфигурацию, написание тестов, работу с асинхронностью, мокирование, снапшоты и специфические сценарии вроде тестирования таймеров или работы с файловой системой.
Я буду ориентироваться на практику: показывать код, разбирать типичные ситуации и объяснять, почему Jest ведет себя именно так. Вы сможете шаг за шагом настроить тестирование в реальном проекте, а не просто увидеть набор изолированных примеров.
Установка и базовая настройка Jest
Установка в проект JavaScript
Для начала вам нужен проект с Node.js и npm или yarn. Допустим, у вас уже есть package.json. Тогда установка выглядит так:
# Установка Jest как dev-зависимости
npm install --save-dev jest
# или
yarn add --dev jest
После установки вам нужно добавить скрипт для запуска тестов в package.json:
{
"scripts": {
// Команда для запуска всех тестов
"test": "jest"
}
}
Теперь вы можете запустить тесты командой:
npm test
# или
yarn test
Если тестов еще нет, Jest просто скажет, что ничего не найдено.
Подключение Jest в TypeScript-проект
Для TypeScript вам понадобится дополнительно ts-jest и типы Jest:
npm install --save-dev jest ts-jest @types/jest typescript
# или
yarn add --dev jest ts-jest @types/jest typescript
Дальше инициализируем ts-jest, чтобы создать конфигурацию:
npx ts-jest config:init
Эта команда создаст файл jest.config.js с базовыми настройками для TypeScript. Он может выглядеть так:
// jest.config.js
module.exports = {
// Говорим Jest, что будем использовать ts-jest как трансформер для TypeScript
preset: 'ts-jest',
// Указываем среду выполнения тестов - по умолчанию это jsdom
testEnvironment: 'node'
};
Здесь я использую testEnvironment: 'node', если вы тестируете backend-код. Для фронтенда (например, React) чаще оставляют jsdom.
В package.json скрипт будет таким же:
{
"scripts": {
"test": "jest"
}
}
Организация файлов тестов
Где хранить тесты
У Jest есть несколько стандартных паттернов поиска тестов. По умолчанию он будет искать:
- файлы, находящиеся в папках
__tests__ - файлы с суффиксом
.test.jsили.spec.js - то же самое для TypeScript —
.test.tsи.spec.ts
Один из популярных вариантов — держать тесты рядом с кодом:
src/math/add.tssrc/math/add.test.ts
Либо использовать отдельную структуру:
src/add.ts__tests__/add.test.ts
Jest найдет оба варианта. Вы можете управлять этим через опцию testMatch в конфиге Jest, но для начала разумно опираться на стандартные паттерны.
Простейший тестовый файл
Создадим простой модуль и напишем к нему тест. Смотрите, я покажу вам, как это выглядит по шагам.
Файл src/sum.js:
// src/sum.js
// Простая функция сложения двух чисел
function sum(a, b) {
return a + b;
}
// Экспортируем функцию, чтобы она была доступна в тестах
module.exports = sum;
Теперь создадим тест src/sum.test.js:
// src/sum.test.js
// Импортируем тестируемую функцию
const sum = require('./sum');
// Описываем группу тестов для функции sum
describe('sum', () => {
// Описываем конкретный тестовый случай
test('складывает два положительных числа', () => {
// Выполняем функцию и сохраняем результат
const result = sum(2, 3);
// Проверяем, что результат равен ожидаемому значению
expect(result).toBe(5);
});
test('корректно работает с отрицательными числами', () => {
const result = sum(-2, -3);
expect(result).toBe(-5);
});
});
Теперь вы можете запустить:
npm test
Jest найдет файл sum.test.js, выполнит тесты и покажет вам, сколько прошло и сколько упало.
Основные функции и структура тестов
describe, test и it
В Jest базовые строительные блоки такие:
describe— группировка тестовtestилиit— конкретный тестexpect— проверка ожидаемого результата
Давайте разберемся на примере:
// user.test.js
// Описываем группу тестов для сущности "Пользователь"
describe('User entity', () => {
// Конкретный тест - проверка отображения полной информации
test('формирует полное имя пользователя', () => {
const user = { firstName: 'Иван', lastName: 'Иванов' };
// Здесь мы проверяем, что строка собрана правильно
const fullName = `${user.firstName} ${user.lastName}`;
expect(fullName).toBe('Иван Иванов');
});
// Альтернативный синтаксис - it вместо test
it('поддерживает пользователей без фамилии', () => {
const user = { firstName: 'Иван' };
// Используем логическое ИЛИ, чтобы подставить пустую строку, если фамилии нет
const fullName = `${user.firstName} ${user.lastName || ''}`.trim();
expect(fullName).toBe('Иван');
});
});
Функции test и it полностью эквивалентны, это просто вопрос стиля.
Структура ассертов с expect
Функция expect принимает фактическое значение и возвращает объект с матчерами (matcher), которые позволяют описать ожидаемое поведение.
Примеры распространенных матчеров:
test('основные матчеры', () => {
const value = 2 + 2;
// Проверка строгого равенства
expect(value).toBe(4);
// Проверка нестрогого равенства (по сути те же semantics, но для NaN используется специальная логика)
expect(value).toEqual(4);
// Проверка булевых выражений
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(4);
expect(value).toBeLessThan(5);
expect(value).toBeLessThanOrEqual(4);
});
test('проверка объектов и массивов', () => {
const user = { name: 'Иван', age: 30 };
const result = ['a', 'b', 'c'];
// Проверка глубокого равенства объектов
expect(user).toEqual({ name: 'Иван', age: 30 });
// Проверка что объект содержит подмножество свойств
expect(user).toMatchObject({ name: 'Иван' });
// Проверка содержания элемента в массиве
expect(result).toContain('b');
});
Смотрите, здесь важно понимать разницу между toBe и toEqual:
toBeиспользует сравнение по ссылке (для объектов) и по значению (для примитивов)toEqualделает глубокое сравнение структур
Для объектов почти всегда используют toEqual.
Проверка ошибок и исключений
Покажу вам, как проверять, что код выбрасывает ошибку:
// errors.test.js
// Функция, которая выбрасывает ошибку при недопустимом значении
function divide(a, b) {
if (b === 0) {
throw new Error('Деление на ноль невозможно');
}
return a / b;
}
test('деление на ноль выбрасывает ошибку', () => {
// Оборачиваем вызов функции в анонимную функцию
// Jest ожидает именно функцию, а не результат ее вызова
const call = () => divide(10, 0);
// Проверяем, что при вызове выбрасывается ошибка
expect(call).toThrow(Error);
// Можно проверить конкретное сообщение
expect(call).toThrow('Деление на ноль невозможно');
});
Такой подход удобен, когда вам важно явно зафиксировать, что в определенных условиях функция должна упасть.
Тестирование асинхронного кода
Асинхронность часто вызывает вопросы, поэтому остановимся на этом подробнее.
Тестирование Promise с async/await
Предположим, у вас есть функция, которая возвращает Promise:
// fetchUser.js
// Имитация асинхронного запроса за данными пользователя
function fetchUser(id) {
return new Promise((resolve) => {
setTimeout(() => {
// Здесь мы возвращаем объект пользователя через 100 мс
resolve({ id, name: 'Иван' });
}, 100);
});
}
module.exports = fetchUser;
Теперь вы увидите, как это выглядит в тесте с async/await:
// fetchUser.test.js
const fetchUser = require('./fetchUser');
test('получение пользователя по id - async/await', async () => {
// Ждем выполнения функции fetchUser
const user = await fetchUser(1);
// Проверяем, что пришли нужные данные
expect(user).toEqual({ id: 1, name: 'Иван' });
});
Jest понимает, что если вы вернули из теста Promise (а async-функция всегда возвращает Promise), то нужно дождаться его завершения перед тем, как считать тест выполненным.
Тестирование отказов (reject) у Promise
Давайте посмотрим, как тестировать ошибки в Promise:
// fetchUserWithError.js
// Функция, которая иногда выбрасывает ошибку (reject)
function fetchUserWithError(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id <= 0) {
// Если id некорректный, отклоняем промис
reject(new Error('Некорректный идентификатор пользователя'));
} else {
resolve({ id, name: 'Иван' });
}
}, 50);
});
}
module.exports = fetchUserWithError;
Теперь тест:
// fetchUserWithError.test.js
const fetchUserWithError = require('./fetchUserWithError');
test('ошибка при некорректном id - async/await', async () => {
// Используем конструкцию rejects для проверки отклонения промиса
await expect(fetchUserWithError(0)).rejects.toThrow(
'Некорректный идентификатор пользователя'
);
});
test('успешное выполнение при корректном id', async () => {
// Используем конструкцию resolves для проверки успешного резолва промиса
await expect(fetchUserWithError(1)).resolves.toEqual({ id: 1, name: 'Иван' });
});
Обратите внимание: в асинхронных тестах важно возвращать Promise (или использовать async/await и await перед expect(...).rejects/ resolves), иначе Jest может завершить тест раньше времени.
Колбэки (callback-style)
В старых библиотеках вы часто встречаете колбэки. Jest поддерживает и этот вариант. Здесь я размещаю пример, чтобы вам было проще понять:
// callbackFn.js
// Функция, которая принимает колбэк и вызывает его через 100 мс
function doSomethingAsync(callback) {
setTimeout(() => {
// Передаем в колбэк результат операции
callback('result');
}, 100);
}
module.exports = doSomethingAsync;
Тест с использованием аргумента done:
// callbackFn.test.js
const doSomethingAsync = require('./callbackFn');
test('колбэк вызывается с корректным результатом', (done) => {
// Вызываем функцию и передаем наш колбэк
doSomethingAsync((data) => {
try {
// Пытаемся выполнить проверку
expect(data).toBe('result');
// Если все хорошо, сигнализируем Jest о завершении теста
done();
} catch (error) {
// Если произошла ошибка, передаем ее в done, чтобы тест упал
done(error);
}
});
});
Использование done нужно, чтобы Jest ждал колбэк и не завершал тест сразу.
Жизненный цикл тестов: beforeEach, afterEach и другие
Jest предоставляет хуки, которые позволяют готовить состояние перед тестами и очищать его после.
Основные хуки
beforeAll— выполняется один раз перед всеми тестами в блокеdescribeafterAll— выполняется один раз после всех тестов в блокеdescribebeforeEach— выполняется перед каждым тестомafterEach— выполняется после каждого теста
Давайте разберемся на примере:
// lifecycle.test.js
let db = [];
// Функция для очистки "базы данных"
function clearDB() {
db = [];
}
// Функция для инициализации фиктивных данных
function seedDB() {
db.push({ id: 1, name: 'Иван' });
db.push({ id: 2, name: 'Мария' });
}
describe('работа с псевдо-базой данных', () => {
// Перед каждым тестом очищаем и наполняем базу начальными данными
beforeEach(() => {
clearDB();
seedDB();
});
// После каждого теста просто очищаем массив
afterEach(() => {
clearDB();
});
test('количество элементов соответствует начальному состоянию', () => {
expect(db.length).toBe(2);
});
test('мы можем добавлять новые элементы', () => {
db.push({ id: 3, name: 'Петр' });
expect(db.length).toBe(3);
expect(db[2].name).toBe('Петр');
});
});
Как видите, этот код выполняет подготовку окружения для каждого теста, чтобы они были независимы друг от друга. Это важный принцип: тесты не должны влиять на состояние последующих тестов.
Мокирование (mock) в Jest
Мокирование — одна из сильных сторон Jest. Оно позволяет подменять зависимости, чтобы изолировать тестируемый модуль.
jest.fn — создание мок-функций
jest.fn создает функцию-заглушку, поведение которой вы можете настраивать.
// mockFn.test.js
test('использование jest.fn', () => {
// Создаем мок-функцию
const callback = jest.fn();
// Передаем мок-функцию, как будто это реальный колбэк
callback('hello');
// Проверяем, что функция была вызвана
expect(callback).toHaveBeenCalled();
// Проверяем, что она была вызвана один раз
expect(callback).toHaveBeenCalledTimes(1);
// Проверяем, с какими аргументами была вызвана функция
expect(callback).toHaveBeenCalledWith('hello');
});
Вы можете задать возвращаемое значение:
test('мок-функция с заданным возвращаемым значением', () => {
// Создаем мок, который всегда возвращает 42
const getAnswer = jest.fn().mockReturnValue(42);
const result = getAnswer();
expect(result).toBe(42);
expect(getAnswer).toHaveBeenCalledTimes(1);
});
Или задать поведение для последовательности вызовов:
test('разное поведение на разные вызовы', () => {
const mock = jest
.fn()
// Первый вызов вернет 1
.mockReturnValueOnce(1)
// Второй вызов вернет 2
.mockReturnValueOnce(2)
// Все дальнейшие вызовы будут возвращать 0
.mockReturnValue(0);
expect(mock()).toBe(1);
expect(mock()).toBe(2);
expect(mock()).toBe(0);
expect(mock()).toBe(0);
});
jest.spyOn — шпионим и частично мокаем
Если вам нужно не просто создать мок-функцию, а подменить реальный метод объекта, удобно использовать jest.spyOn.
// spyOn.test.js
const logger = {
// Метод логирования, который мы будем "подглядывать"
log: (message) => {
console.log(message);
}
};
function doWork() {
// В рабочем коде вызывается логгер
logger.log('Работа выполнена');
}
test('doWork вызывает logger.log', () => {
// Создаем шпиона на методе logger.log
const spy = jest.spyOn(logger, 'log').mockImplementation(() => {
// Здесь мы подменяем оригинальную реализацию на пустую
// чтобы не печатать ничего в консоль во время теста
});
// Вызываем тестируемую функцию
doWork();
// Проверяем, что логгер был вызван
expect(spy).toHaveBeenCalledWith('Работа выполнена');
// Восстанавливаем оригинальную реализацию после теста
spy.mockRestore();
});
Здесь вы контролируете, как ведет себя метод во время теста, но при этом можете вернуть оригинальное поведение после.
jest.mock — мокирование модулей
Теперь давайте посмотрим, как можно замокать целый модуль. Допустим, у вас есть модуль, который делает HTTP-запрос, а вы хотите тестировать только логику, не трогая сеть.
// api.js
// Псевдо-функция, которая делает запрос куда-то во внешний сервис
async function fetchData() {
// В реальном коде здесь был бы вызов fetch или axios
return { status: 'ok', data: [1, 2, 3] };
}
module.exports = { fetchData };
И модуль, который использует этот API:
// service.js
const { fetchData } = require('./api');
// Функция, которую мы хотим протестировать
async function getProcessedData() {
const response = await fetchData();
// Если ответ не ок, бросаем ошибку
if (response.status !== 'ok') {
throw new Error('Ошибка при получении данных');
}
// Возвращаем только массив данных
return response.data;
}
module.exports = { getProcessedData };
Теперь вы увидите, как это тестируется с мокированием модуля api:
// service.test.js
// Сначала говорим Jest, что модуль ./api нужно замокать
jest.mock('./api');
const { getProcessedData } = require('./service');
// После jest.mock мы можем импортировать замоканную версию
const { fetchData } = require('./api');
test('getProcessedData использует fetchData и возвращает массив', async () => {
// Настраиваем, что замоканный fetchData вернет нужный объект
fetchData.mockResolvedValue({ status: 'ok', data: [10, 20, 30] });
const result = await getProcessedData();
expect(fetchData).toHaveBeenCalledTimes(1);
expect(result).toEqual([10, 20, 30]);
});
test('getProcessedData выбрасывает ошибку при status не ok', async () => {
fetchData.mockResolvedValue({ status: 'error', data: [] });
// Здесь мы проверяем, что функция отклоняется с ошибкой
await expect(getProcessedData()).rejects.toThrow(
'Ошибка при получении данных'
);
});
Обратите внимание на порядок: сначала вызываем jest.mock('./api'), потом импортируем service и только затем используем мокированный fetchData. Это важно, потому что service подхватывает уже замоканную версию.
Тестирование React-компонентов с Jest и Testing Library
Очень часто Jest используют вместе с React Testing Library. Смотрите, как это выглядит.
Базовая настройка
В проекте на React (Create React App или Vite) Jest often уже настроен. Но если вы добавляете его сами, вам понадобится:
npm install --save-dev @testing-library/react @testing-library/jest-dom
Подключите @testing-library/jest-dom в одном из файлов, которые запускаются перед тестами (например, setupTests.js):
// setupTests.js
// Добавляем дополнительные матчеры, такие как toBeInTheDocument
import '@testing-library/jest-dom';
И укажите setupFilesAfterEnv в jest.config:
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/setupTests.js']
};
Простой тест React-компонента
Давайте посмотрим, что происходит в этом примере:
// Counter.jsx
import React, { useState } from 'react';
export function Counter() {
// Локальное состояние счетчика
const [count, setCount] = useState(0);
// Обработчик клика по кнопке
const handleClick = () => {
setCount((prev) => prev + 1);
};
return (
<div>
{/* Текущее значение счетчика */}
<span>Текущее значение {count}</span>
{/* Кнопка, нажатие на которую увеличивает счетчик */}
<button onClick={handleClick}>Увеличить</button>
</div>
);
}
А вот тест:
// Counter.test.jsx
import React from 'react';
// Импортируем функции для рендеринга и взаимодействия с компонентом
import { render, screen, fireEvent } from '@testing-library/react';
// Импортируем тестируемый компонент
import { Counter } from './Counter';
test('счетчик отображает начальное значение и увеличивается при клике', () => {
// Рендерим компонент в виртуальном DOM
render(<Counter />);
// Ищем элемент по тексту
const spanElement = screen.getByText(/Текущее значение/i);
// Проверяем, что изначально значение равно 0
expect(spanElement).toHaveTextContent('Текущее значение 0');
// Находим кнопку по тексту
const button = screen.getByText('Увеличить');
// Имитируем клик по кнопке
fireEvent.click(button);
// Проверяем, что текст обновился и теперь значение равно 1
expect(spanElement).toHaveTextContent('Текущее значение 1');
});
React Testing Library ориентирована на тестирование поведения с точки зрения пользователя, а Jest здесь выступает как тестовый раннер и фреймворк ассертов.
Снапшот-тестирование
Снапшоты помогают отслеживать изменения в структуре результата, например в JSX-компонентах или выходе функции.
Снапшоты для компонентов
Пример с React:
// Hello.jsx
import React from 'react';
// Простой компонент, который показывает приветствие
export function Hello({ name }) {
return <h1>Привет, {name}</h1>;
}
Тест со снапшотом:
// Hello.test.jsx
import React from 'react';
// Импортируем метод для рендеринга в "дерево" без браузера
import renderer from 'react-test-renderer';
import { Hello } from './Hello';
test('компонент Hello соответствует снепшоту', () => {
// Создаем тестовый рендер компонента
const tree = renderer.create(<Hello name="Мир" />).toJSON();
// Сравниваем с существующим снапшотом
// Если снапшота нет, Jest создаст его автоматически при первом запуске
expect(tree).toMatchSnapshot();
});
Когда вы запускаете тест первый раз, Jest создает файл со снапшотом, где хранится JSON-описание структуры компонента. При последующих запусках Jest сравнивает текущий результат с сохраненным.
Если вы осознанно поменяли разметку, вы можете обновить снапшоты командой:
npm test -- --updateSnapshot
Или сокращенно:
npm test -- -u
Снапшоты для обычных функций
Снапшот можно использовать и для объектов:
// formatUser.js
// Функция форматирования данных пользователя для UI
function formatUser(rawUser) {
return {
id: rawUser.id,
fullName: `${rawUser.firstName} ${rawUser.lastName}`,
isAdult: rawUser.age >= 18
};
}
module.exports = { formatUser };
Тест:
// formatUser.test.js
const { formatUser } = require('./formatUser');
test('formatUser возвращает корректный объект', () => {
const raw = { id: 1, firstName: 'Иван', lastName: 'Иванов', age: 20 };
const formatted = formatUser(raw);
// Сравниваем результат с сохраненным снапшотом
expect(formatted).toMatchSnapshot();
});
Снапшоты удобны, когда структура объектов сложная и перечислять все поля в тесте вручную неудобно. Но важно использовать их осознанно: слишком много снапшотов делает тесты хрупкими и тяжело поддерживаемыми.
Работа с таймерами: setTimeout и setInterval
При тестировании кода с таймерами (setTimeout, setInterval) удобно использовать фейковые таймеры.
Включение фейковых таймеров
Покажу вам, как это реализовано на практике:
// timers.js
// Функция, которая через задержку вызывает колбэк
function delayedCall(callback) {
setTimeout(() => {
callback('done');
}, 1000);
}
module.exports = { delayedCall };
Тест с использованием jest.useFakeTimers:
// timers.test.js
const { delayedCall } = require('./timers');
test('delayedCall вызывает колбэк через 1 секунду', () => {
// Включаем фейковые таймеры
jest.useFakeTimers();
// Создаем мок-колбэк
const callback = jest.fn();
// Вызываем тестируемую функцию
delayedCall(callback);
// На этом этапе таймер еще не сработал
expect(callback).not.toHaveBeenCalled();
// Перематываем все таймеры вперед
jest.runAllTimers();
// Теперь колбэк должен был вызваться
expect(callback).toHaveBeenCalledWith('done');
// Возвращаем реальные таймеры после теста (необязательно в простых случаях, но полезно в больших проектах)
jest.useRealTimers();
});
Здесь Jest перехватывает вызовы setTimeout и управляет ими сам. Вы можете:
jest.runAllTimers()— выполнить все таймерыjest.advanceTimersByTime(ms)— перемотать время вперед на указанное количество миллисекундjest.runOnlyPendingTimers()— выполнить только ожидающие таймеры
Конфигурация Jest
Jest можно конфигурировать через файл jest.config.js, jest.config.cjs, package.json или jest.config.mjs. Давайте разберем ключевые опции.
Пример базовой конфигурации
// jest.config.js
module.exports = {
// Базовая директория проекта
rootDir: '.',
// Окружение, в котором выполняются тесты
// node - для бэкенда, jsdom - для фронтенда
testEnvironment: 'node',
// Паттерн для поиска тестов
testMatch: ['**/?(*.)+(test).[jt]s?(x)'],
// Игнорируемые директории
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
// Покрытие кода
collectCoverage: true,
collectCoverageFrom: ['src/**/*.{js,jsx}', '!src/index.js'],
// Папка для отчетов по покрытию
coverageDirectory: 'coverage',
// Модули, которые нужно замокать или трансформировать, можно настроить здесь
// transform, moduleNameMapper и другие
};
Каждая опция влияет на поведение раннера. Например, collectCoverage включает сбор покрытия, но немного замедляет выполнение тестов.
Конфигурация для TypeScript
Для TypeScript-проекта с ts-jest типичный конфиг может выглядеть так:
// jest.config.js
module.exports = {
preset: 'ts-jest', // Используем ts-jest как пресет
testEnvironment: 'node',
testMatch: ['**/?(*.)+(test).[tj]s?(x)'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/index.ts'],
coverageDirectory: 'coverage'
};
ts-jest сам позаботится о том, чтобы транспилировать TypeScript в JS перед запуском тестов.
Запуск, фильтрация и наблюдение за тестами
Запуск отдельных тестов
Иногда вам нужно запустить только один тест или одну группу. У Jest для этого есть test.only и describe.only.
// only.test.js
describe('группа тестов', () => {
test('этот тест будет пропущен', () => {
expect(1 + 1).toBe(3); // Этот тест не выполнится
});
test.only('этот тест будет выполнен', () => {
expect(1 + 1).toBe(2);
});
});
Пока в коде есть .only, Jest будет запускать только помеченные тесты. Это удобно для локальной отладки, но важно не оставлять .only в коммитах.
Аналогично есть test.skip и describe.skip — они помечают тесты как пропущенные.
Фильтрация по имени
Вы можете запустить только те тесты, в имени которых встречается определенная подстрока:
npm test -- user
Jest попробует найти все тесты, в имени которых есть user. Это касается и имён файлов, и названий describe/test.
Режим watch
В режиме наблюдения Jest будет запускать только тесты, связанные с измененными файлами, и обновлять результаты на лету.
npm test -- --watch
Дальше вы можете использовать горячие клавиши (например, p для фильтрации по названию, t — по имени теста, a — запустить все тесты).
Практические советы и лучшие практики
Делайте тесты независимыми
Каждый тест должен:
- не зависеть от результатов других тестов
- уметь запускаться отдельно
- не требовать специфического порядка выполнения
Для этого:
- используйте
beforeEachиafterEachдля подготовки/очистки состояния - избегайте глобального изменяемого состояния
- каждый тест должен сам создавать все необходимое окружение
Тестируйте поведение, а не реализацию
Хорошие тесты проверяют, что код делает, а не то, как он это делает внутри.
Например, вместо:
- проверок количества внутренних вызовов приватных функций (если это не критично)
- избыточного использования моков, которые полностью повторяют реализацию
Лучше:
- проверять результат работы (возвращаемые значения, состояние БД, изменения в DOM)
- мокать только внешние зависимости (сеть, файловая система, случайные значения)
Не перегружайте снапшот-тестами
Снапшоты полезны, когда:
- структура результата сложная
- вы ожидаете редкие, но значимые изменения
Но если:
- снапшоты большие
- они часто меняются при небольших правках
- вы механически жмете "обновить снапшоты", не анализируя изменения
такие тесты теряют смысл. Старайтесь держать снапшоты маленькими и осмысленными.
Поддерживайте тесты в актуальном состоянии
Если тесты начинают падать при любом изменении кода, разработчики со временем перестают им доверять и либо отключают их, либо обновляют "на глаз", не вникая в суть. Это снижает ценность всего набора тестов.
Следите, чтобы:
- тесты были стабильными (без случайных падений)
- при изменении требований вы обновляли тесты вместе с кодом
- тесты читались как спецификация поведения
Хорошее покрытие тестами на Jest помогает быстрее находить регрессии, уверенно рефакторить код и уменьшать количество критичных ошибок в продакшене. По мере того как вы осваиваете Jest, имеет смысл постепенно вводить более продвинутые техники — сложное мокирование, снапшоты, интеграцию с CI и анализ покрытия.
Частозадаваемые технические вопросы по теме статьи и ответы на них
Как запустить Jest только для файлов в конкретной директории
Вы можете использовать флаг --runTestsByPath или просто указать путь:
npx jest src/services
# или конкретный файл
npx jest src/services/user.service.test.ts
В скриптах package.json это работает так же:
{
"scripts": {
"test:services": "jest src/services"
}
}
Как настроить алиасы путей (import из @src вместо относительных путей) в Jest
Используйте опцию moduleNameMapper:
// jest.config.js
module.exports = {
moduleNameMapper: {
'^@src/(.*)$': '<rootDir>/src/$1'
}
};
Теперь импорт @src/utils/math в тестах будет сопоставляться с src/utils/math.
Как делать снапшоты с учетом форматирования даты и случайных значений
Обычно такие значения делают предсказуемыми:
- Мокируйте
Date.nowилиnew Dateс помощьюjest.spyOnиmockImplementation. - Мокируйте
Math.random:
jest.spyOn(global.Math, 'random').mockReturnValue(0.5);
- В
afterEachилиafterAllвосстанавливайте оригинальную реализацию черезmockRestore.
Так снапшоты будут стабильными.
Как запускать тесты Jest в Docker-контейнере
- Установите зависимости внутри контейнера.
- Добавьте в Dockerfile:
RUN npm ci
CMD ["npm", "test", "--", "--runInBand"]
Опция --runInBand отключает параллелизм, что повышает стабильность в ограниченных по ресурсам контейнерах.
Как интегрировать Jest с ESLint, чтобы запрещать .only в тестах
Установите плагин:
npm install --save-dev eslint-plugin-jest
В конфигурации ESLint:
// .eslintrc.js
module.exports = {
plugins: ['jest'],
extends: ['plugin:jest/recommended'],
rules: {
'jest/no-focused-tests': 'error' // запрет test.only и describe.only
}
};
Теперь линтер будет ругаться, если в коде останутся "сфокусированные" тесты.
Постройте личный план изучения Vue до уровня Middle — бесплатно!
Vue — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Vue
Лучшие курсы по теме

Vue 3 и Pinia
Антон Ларичев
TypeScript с нуля
Антон Ларичев