Настройка Jest

Jest — самый популярный фреймворк для тестирования JavaScript. Работает «из коробки» с минимальной настройкой.

Зачем нужно

Jest объединяет test runner, assertion library и mocking в одном пакете. Не нужно собирать по частям (Mocha + Chai + Sinon). Поддерживает snapshot-тесты, параллельное выполнение, coverage — всё включено.

Где используется

React-проекты (create-react-app включает Jest по умолчанию), Node.js-сервисы, npm-пакеты, любые JavaScript/TypeScript проекты.

Предпосылки

Основы тестирования, Node.js и npm установлены

Установка

Базовая установка

# npm
npm install --save-dev jest

# yarn
yarn add --dev jest

# pnpm
pnpm add -D jest

Для TypeScript

npm install --save-dev jest ts-jest @types/jest

Для ES Modules (import/export)

npm install --save-dev jest @babel/core @babel/preset-env babel-jest

Настройка package.json

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:verbose": "jest --verbose",
    "test:ci": "jest --ci --coverage --maxWorkers=2"
  }
}

jest.config.js

Минимальная конфигурация

// jest.config.js
module.exports = {
  // Где искать тесты
  testMatch: [
    '**/__tests__/**/*.js',
    '**/*.test.js',
    '**/*.spec.js',
  ],

  // Какие файлы считать тестовыми
  testPathIgnorePatterns: ['/node_modules/', '/dist/'],
};

Полная конфигурация

// jest.config.js
module.exports = {
  // === Поиск тестов ===
  roots: ['<rootDir>/src'],
  testMatch: ['**/__tests__/**/*.{js,ts}', '**/*.{test,spec}.{js,ts}'],
  testPathIgnorePatterns: ['/node_modules/', '/dist/', '/build/'],

  // === Трансформация ===
  transform: {
    '^.+\\.tsx?$': 'ts-jest',          // TypeScript
    '^.+\\.jsx?$': 'babel-jest',       // JSX/JS
  },

  // === Модули ===
  moduleNameMapper: {
    // Алиасы путей (как в webpack/tsconfig)
    '^@/(.*)$': '<rootDir>/src/$1',
    '^@components/(.*)$': '<rootDir>/src/components/$1',
    '^@utils/(.*)$': '<rootDir>/src/utils/$1',

    // Заглушки для не-JS файлов
    '\\.(css|less|scss)$': 'identity-obj-proxy',
    '\\.(jpg|jpeg|png|gif|svg)$': '<rootDir>/__mocks__/fileMock.js',
  },
  moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json'],

  // === Окружение ===
  testEnvironment: 'node',  // 'jsdom' для браузерного кода

  // === Setup ===
  setupFiles: ['<rootDir>/jest.setup.js'],           // До каждого теста
  setupFilesAfterFramework: ,
  globalSetup: '<rootDir>/jest.globalSetup.js',       // Один раз перед всеми
  globalTeardown: '<rootDir>/jest.globalTeardown.js', // Один раз после всех

  // === Coverage ===
  collectCoverageFrom: [
    'src/**/*.{js,ts}',
    '!src/**/*.d.ts',
    '!src/index.ts',
    '!src/**/__tests__/**',
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
  },
  coverageReporters: ['text', 'lcov', 'clover'],

  // === Производительность ===
  maxWorkers: '50%',   // Количество потоков
  verbose: true,        // Подробный вывод
};

Конфигурация для TypeScript (ts-jest)

// jest.config.ts
import type { Config } from 'jest';

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
};

export default config;

babel.config.js (для ES Modules)

// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env', { targets: { node: 'current' } }],
  ],
};

Структура файлов

src/
├── utils/
│   ├── math.js
│   ├── math.test.js          ← Тест рядом с файлом
│   ├── string.js
│   └── __tests__/
│       └── string.test.js    ← Тесты в папке __tests__
├── services/
│   ├── userService.js
│   └── userService.spec.js   ← .spec.js тоже работает
└── __mocks__/
    ├── axios.js              ← Ручной мок модуля
    └── fileMock.js           ← Заглушка для файлов

fileMock.js

// __mocks__/fileMock.js
module.exports = 'test-file-stub';

Режимы запуска

Watch mode — перезапуск при изменениях

jest --watch        # Только изменённые файлы (нужен git)
jest --watchAll     # Все тесты при любом изменении

Горячие клавиши в watch mode:

  • p — фильтр по имени файла
  • t — фильтр по имени теста
  • f — запустить только упавшие
  • a — запустить все
  • q — выйти

Coverage — покрытие кода

jest --coverage

Результат:

----------|---------|----------|---------|---------|
File      | % Stmts | % Branch | % Funcs | % Lines |
----------|---------|----------|---------|---------|
math.js   |   100   |   87.5   |   100   |   100   |
string.js |    75   |    50    |   66.7  |    75   |
----------|---------|----------|---------|---------|

Запуск конкретных тестов

jest math                    # Файлы, содержащие "math"
jest --testPathPattern=utils # Файлы в путях с "utils"
jest -t "сложение"           # Тесты с именем "сложение"
jest path/to/file.test.js    # Конкретный файл

Setup и Teardown

// jest.setup.js — выполняется перед каждым тестовым файлом
require('dotenv').config({ path: '.env.test' });

// Глобальные моки
jest.setTimeout(10000); // Таймаут для всех тестов
// В тестовом файле
beforeAll(() => {
  // Один раз перед всеми тестами в файле
  // Подключение к тестовой БД
});

afterAll(() => {
  // Один раз после всех тестов
  // Закрытие соединения
});

beforeEach(() => {
  // Перед каждым тестом
  // Очистка данных
});

afterEach(() => {
  // После каждого теста
  jest.clearAllMocks;
});

Частые ошибки

1. Забыл transform для TypeScript

SyntaxError: Cannot use import statement outside a module

Решение: добавить ts-jest или babel-jest в transform.

2. CSS/изображения ломают тесты

SyntaxError: Unexpected token '.'  (при import './styles.css')

Решение: moduleNameMapper для CSS и файлов.

3. Алиасы путей не работают

Cannot find module '@/utils/math'

Решение: moduleNameMapper должен совпадать с tsconfig.json paths.

4. Тесты медленные

// Проверь:
// 1. maxWorkers: '50%' — параллелизм
// 2. Нет реальных HTTP/DB вызовов в unit-тестах
// 3. jest --bail — остановка при первом фейле

Практика

  1. Создай новый проект с Jest: npm init -y && npm i -D jest
  2. Настрой jest.config.js с roots, testMatch, verbose
  3. Добавь скрипты test, test:watch, test:coverage в package.json
  4. Создай mock для CSS-файлов через moduleNameMapper
  5. Настрой coverageThreshold на 80% и убедись, что тесты не проходят при недостаточном покрытии

Связанные темы

Ресурсы