Jest — фреймворк для тестов на JavaScript, который ставится одной зависимостью и работает без браузера. Он даёт раннер, матчеры, моки, покрытие и снепшоты в одном пакете. Но именно из-за принципа «всё включено» новички тонут в конфигурации и в странных ошибках про ESM. Разберём рабочий минимум: от установки до покрытия в CI.

Установка и инициализация

mkdir shop && cd shop
npm init -y
npm install --save-dev jest
npx jest --init

Мастер задаст несколько вопросов: окружение (Node или jsdom), покрытие, очистка моков. Файл jest.config.js можно написать руками — он читается понятнее, чем десяток ключей в package.json. Добавьте скрипт "test": "jest", чтобы команда была одинаковой локально и в CI.

Ветка Jest 30 — актуальная; в старых проектах встречается 29, и большинство команд совпадает. Проверить версию: npx jest --version.

Конфигурация, которая действительно нужна

module.exports = {
  testEnvironment: 'node',
  testMatch: ['**/__tests__/**/*.test.js'],
  clearMocks: true,
  restoreMocks: true,
  collectCoverageFrom: ['src/**/*.js'],
  coverageThreshold: { global: { lines: 80, branches: 70 } },
};

testEnvironment определяет глобальные объекты: в node нет document, в jsdom есть поддельный DOM. Для тестов React и работы с DOM ставьте jest-environment-jsdom — с версии 28 он не входит в основной пакет. clearMocks и restoreMocks снимают классическую проблему «тест проходит один, но падает в наборе»: состояние моков не перетекает между тестами.

Первый тест и матчеры

Тест состоит из describe, it и проверок expect. Имя должно описывать поведение, а не функцию: «возвращает скидку для повторного заказа» лучше, чем «тест calculate».

const { calcTotal } = require('../src/cart');

describe('calcTotal', () => {
  it('суммирует позиции и применяет скидку', () => {
    const items = [{ price: 100, qty: 2 }, { price: 50, qty: 1 }];
    expect(calcTotal(items, 0.1)).toBe(225);
  });

  it('возвращает 0 для пустой корзины', () => {
    expect(calcTotal([], 0.1)).toBe(0);
  });
});

toBe сравнивает примитивы, toEqual — структуры, toStrictEqual дополнительно проверяет типы и отсутствие лишних полей. Для объектов используйте toMatchObject, для исключений — toThrow(/текст/). Не смешивайте toBe с объектами: он сравнивает ссылки и даст ложное падение.

Для однотипных проверок используйте test.each: он печатает входные данные в имени теста и не плодит копипасту. Подготовку и очистку выносите в beforeEach и afterEach, а общие данные создавайте фабрикой, чтобы тесты не меняли один и тот же объект.

Моки и шпионы

Тест не должен ходить в сеть или в базу. Подменяйте границы модуля: функцию, HTTP-клиент, модуль целиком.

const mailer = require('../src/mailer');
jest.mock('../src/mailer');

test('отправляет письмо после регистрации', async () => {
  mailer.send.mockResolvedValue({ ok: true });
  await register({ email: 'user@example.org' });
  expect(mailer.send).toHaveBeenCalledWith(
    expect.objectContaining({ to: 'user@example.org' })
  );
});

jest.mock поднимается в начало файла, поэтому переменные внутри фабрики должны начинаться с mock — иначе Jest выдаст ошибку про область видимости. Для точечной подмены одного метода есть jest.spyOn(object, 'method') и mockRestore(). Общие заглушки кладут в каталог __mocks__ рядом с модулем.

Асинхронность и таймеры

Асинхронный тест обязан вернуть промис или использовать async/await. Забытый await — самая частая причина «зелёного» теста, который ничего не проверил.

test('загружает заказ', async () => {
  await expect(fetchOrder(1)).resolves.toMatchObject({ id: 1 });
  await expect(fetchOrder(-1)).rejects.toThrow('not found');
});

Для setTimeout и Date включайте фейковые таймеры: jest.useFakeTimers(), затем jest.advanceTimersByTime(1000). Время можно зафиксировать через jest.setSystemTime(new Date('2026-01-01')). Не забудьте вернуть реальные таймеры в afterEach, иначе следующие тесты будут висеть.

TypeScript, ESM и DOM

Для TypeScript удобен ts-jest: он проверяет типы и транспилирует файлы. Альтернатива — @swc/jest, который быстрее, но не проверяет типы.

npm install --save-dev ts-jest @types/jest typescript
npx ts-jest config:init
npx jest --listTests
NODE_OPTIONS=--experimental-vm-modules npx jest

В конфиге для ts-jest укажите preset: 'ts-jest' и testEnvironment: 'node'. Для нативного ESM нужен флаг --experimental-vm-modules и extensionsToTreatAsEsm; проще не смешивать CommonJS и ESM в одном проекте. Для компонентов добавьте @testing-library/dom и jest-environment-jsdom, но помните: jsdom не считает раскладку и не отрисовывает страницу — проверяйте поведение, а не пиксели.

Покрытие и CI

npx jest --coverage
npx jest --ci --runInBand --coverage
npx jest --watch
npx jest -t 'скидк' -u

--ci запрещает интерактивные обновления снепшотов, --runInBand запускает тесты последовательно — полезно в контейнерах с малым объёмом памяти. -t фильтрует по имени, -u обновляет снепшоты. Порог покрытия в конфиге превращает падение качества в падение сборки, а не в устное замечание на ревью.

Снепшоты держите небольшими: огромный дамп HTML невозможно осмысленно ревьюить, и его обновляют не глядя. Для компонентов лучше явные проверки текста и ролей.

Типичные ошибки и диагностика

  • «Cannot use import statement outside a module» — файл с ESM попал в CommonJS-окружение. Проверьте type в package.json и трансформацию.
  • «Jest did not exit one second after…» — остался открытый таймер, сокет или подписка. Запустите с --detectOpenHandles, найдите источник; --forceExit маскирует проблему.
  • «ReferenceError: fetch is not defined» — окружение не содержит глобального fetch. Добавьте полифил в setupFiles или обновите Node.
  • Тест проходит отдельно и падает в наборе — общее состояние, неочищенные моки или jest.setSystemTime без сброса.
node --inspect-brk node_modules/.bin/jest --runInBand
npx jest --listTests
npx jest --showConfig | head -40
npx jest --detectOpenHandles

Отладчик останавливается на первой строке; дальше открывайте chrome://inspect. --listTests показывает, какие файлы Jest вообще видит — это быстрее, чем гадать, почему новый тест не запускается. --showConfig печатает итоговую конфигурацию после слияния всех источников.

Итог

Jest хорош, когда проект не требует сложной сборки: конфиг на десять строк, понятные моки и покрытие из коробки. Начните с окружения node, добавьте clearMocks, напишите тесты на чистые функции, затем переходите к интеграционным. ESM и TypeScript подключайте осознанно, а не копированием конфига из чужого репозитория. И не гонитесь за процентом покрытия: тест, который ничего не проверяет, хуже отсутствующего.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте