логотип PurpleSchool
логотип PurpleSchool

Руководство по тестированию TypeScript с Jest

Автор

Дмитрий Кулаков

Введение

Тестирование TypeScript-кода — одна из самых важных практик для любого современного проекта на JavaScript или Node.js. С помощью автоматических тестов вы быстро выявляете ошибки, не боитесь рефакторинга кода и уверенно выпускаете новые версии приложения. Для тестирования TypeScript почти стандартом де-факто считается Jest — быстрый и мощный тестовый фреймворк.

В этом руководстве вы узнаете, как настроить Jest для работы с TypeScript, на что обратить внимание при написании тестов, какие возможности предоставляет Jest и каким образом эффективно организовать тестовую среду. Я покажу подходы ко всему — от запуска первого теста до мокирования зависимостей. Каждый из этапов будет сопровождаться кодом и объяснениями, чтобы вы могли сразу повторить все на практике.

Почему Jest для TypeScript

Стоит начать с главного вопроса — почему так часто выбирают Jest. Причины просты:

  • Jest прекрасно интегрируется с TypeScript и позволяет использовать самые инженерные преимущества типизации в тестах.
  • Встроенные мок- и spy-функции делают тестирование изолированных модулей удобнее.
  • Быстрый запуск: Jest умеет запускать только те тесты, что требуют повторной проверки, а настройки watch-mode делают работу очень продуктивной.
  • Большое сообщество — значит, все ошибки и нестандартные кейсы уже где-то обсуждались.

Теперь давайте разберёмся, как это работает на практике.

Установка и настройка Jest с TypeScript

Для начала создайте проект или используйте уже существующий. В этом разделе я покажу самый типичный способ настройки с нуля.

Установка зависимостей

Выполните в терминале:

npm install --save-dev jest ts-jest @types/jest typescript
  • jest — сам фреймворк для тестирования.
  • ts-jest — пакет, который позволяет Jest "понимать" TypeScript, трансформируя код "на лету".
  • @types/jest — типы для autocompletion и строгой типизации тестов.
  • typescript — собственно, сам язык.

Настройка конфигурации

Создайте файл jest.config.ts или jest.config.js в корне проекта. Я советую использовать TypeScript для большей гибкости:

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

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  testMatch: ['**/?(*.)+(spec|test).[tj]s?(x)'],
  moduleFileExtensions: ['ts', 'js', 'json', 'node'],
}

export default config
  • preset: 'ts-jest' — ключевой пункт, чтобы работал TypeScript.
  • testEnvironment: 'node' — если вы тестируете серверный код. Для frontend (React и прочих) используйте jsdom.
  • testMatch — шаблон поиска файлов с тестами.
  • moduleFileExtensions — разрешённые расширения файлов.

Если у вас jest.config.js, экспортируйте как обычный объект.

module.exports = { ... }

Конфигурирование TypeScript для тестов

Обновите tsconfig.json, чтобы тестовые файлы и необходимые типы входили в область видимости:

{
  "compilerOptions": {
    "target": "ES2019",
    "module": "commonjs",
    "esModuleInterop": true,
    "declaration": false,
    "noEmit": true,
    "strict": true,
    "types": ["jest", "node"]
  },
  "include": ["src", "tests"]
}

Обратите внимание: добавили types: ["jest", "node"] для корректной работы intellisense в тестах.

Написание простейших тестов на TypeScript с Jest

Первая цель — убедиться, что всё работает. Давайте напишем простую функцию и протестируем её.

Пример обычной функции

Создаём файл:

// src/math.ts

/**
 * Складывает два числа
 * @param a Первое число
 * @param b Второе число
 */
export function sum(a: number, b: number): number {
  return a + b
}

Теперь подготовим тест:

// tests/math.test.ts

import { sum } from '../src/math'

describe('Функция sum', () => {
  it('складывает два положительных числа', () => {
    expect(sum(2, 3)).toBe(5) // Ожидаем, что результат будет 5
  })

  it('работает с отрицательными числами', () => {
    expect(sum(-2, -3)).toBe(-5) // Ожидаем, что результат будет -5
  })
})
  • describe группирует тесты по логическому смыслу.
  • it определяет отдельный сценарий.
  • expect — ассертер, который проверяет результат выполнения функции.

Как запускать тесты

Добавьте в package.json скрипт:

"scripts": {
  "test": "jest"
}

Запустите:

npm test

На экране вы увидите, выполнены ли тесты и сколько из них прошли успешно.

Как работает ts-jest

Когда вы используете TypeScript вместе с Jest, ваш код должен быть скомпилирован из TS в JS "на лету". Именно за это отвечает ts-jest. Фреймворк принимает .ts файлы, обрабатывает их через TypeScript компилятор и уже затем выполняет тесты.

Вы тем самым не обязаны собирать отдельный JS-бандл перед тестированием; все работает "прозрачно".

В некоторых случаях можно явно указывать опции в самом jest.config.ts:

preset: 'ts-jest',
globals: {
  'ts-jest': {
    tsconfig: 'tsconfig.json'
  }
}

Этот блок пригодится, если у вас отдельный tsconfig для тестов.

Mock-функции и мокирование зависимостей

В реальных проектах очень часто приходится тестировать функции и модули, которые зависят от других сервисов, баз данных, API. В таких случаях важно "подменить" эти модули моками, чтобы тестировать только вашу логику, не затрагивая сторонние вызовы.

Пример: мокирование обычной функции

Давайте представим такой случай. У вас есть модуль, который вызывает какую-то внешнюю функцию:

// src/utils.ts
export function getRandomNumber(): number {
  return Math.random() // Функция возвращает случайное число
}

И есть другая функция, которая зависит от неё:

// src/lottery.ts
import { getRandomNumber } from './utils'

export function isWinner(): boolean {
  return getRandomNumber() > 0.5
}

Когда вы будете тестировать isWinner, правильнее использовать мок, чтобы поведение функции было предсказуемым.

// tests/lottery.test.ts
import * as utils from '../src/utils'
import { isWinner } from '../src/lottery'

describe('lottery.isWinner', () => {
  it('возвращает true, если getRandomNumber больше 0.5', () => {
    jest.spyOn(utils, 'getRandomNumber').mockReturnValue(0.7) // Мокируем, что функция вернёт 0.7
    expect(isWinner()).toBe(true)
  })
  it('возвращает false, если getRandomNumber меньше либо равно 0.5', () => {
    jest.spyOn(utils, 'getRandomNumber').mockReturnValue(0.2)
    expect(isWinner()).toBe(false)
  })
})

Команда jest.spyOn(...).mockReturnValue(...) "подменяет" вызов функции во время теста.

Mock классов и объектов

Jest может мокировать и классы. Вот простой пример:

// src/service.ts
export class HttpService {
  fetch(url: string): Promise<string> {
    // ... реальный HTTP-запрос
    return fetch(url).then(res => res.text())
  }
}

Тестируем код, который зависит от этого класса:

// tests/service.test.ts
import { HttpService } from '../src/service'

jest.mock('../src/service')

describe('Пример мокирования класса', () => {
  it('мокируем fetch', async () => {
    // Подменяем реализацию метода fetch
    (HttpService.prototype.fetch as jest.Mock).mockResolvedValue('mocked result')

    const svc = new HttpService()
    const result = await svc.fetch('http://example.com')
    expect(result).toBe('mocked result')
  })
})
  • Здесь используется jest.mock для мокирования модуля.
  • Затем через (HttpService.prototype.fetch as jest.Mock) заменяем реализацию.

Мок-функции позволяют контролировать любые побочные действия, изолировать unit-тесты от сети, базы данных и т.д.

Снапшот-тестирование с TypeScript

Jest умеет не только сравнивать значения, но и сохранять "снимки" (snapshot) вывода функций или компонентов. Это очень полезно для тотального контроля над генераторами кода, сериализаторами, или UI-компонентами.

Пример для объектов

// src/user.ts
export function getUserProfile(id: number) {
  // Обычно здесь запрос к БД, а мы возвращаем фиктивного пользователя:
  return { id, name: 'Иван', age: 30 }
}

Тест со снапшотом:

// tests/user.test.ts
import { getUserProfile } from '../src/user'

describe('getUserProfile', () => {
  it('корректно возвращает профиль пользователя', () => {
    expect(getUserProfile(1)).toMatchSnapshot()
  })
})
  • Первая проверка записывает снапшот в файл.
  • Дальнейшие — сравнивают выходные данные с этим файлом.
  • Если интерфейс объекта изменится, Jest покажет, что тест не прошёл.

Как обновлять снапшоты

Если ваше API изменилось осознанно, и вы хотите обновить снапшоты:

npm test -- -u

Флаг -u обновит все снапшоты.

Code Coverage — измерение покрытия кода

Jest может собирать статистику, насколько ваш код покрыт тестами. Давайте рассмотрим, как это подключить.

Как собрать coverage

Выполните:

npm test -- --coverage

Вы увидите подробный отчёт с процентами, какими частями кода охвачены тесты.

Конфигурация отчёта

В jest.config.ts можно добавить:

collectCoverage: true,
collectCoverageFrom: ['src/**/*.ts'],
coverageDirectory: 'coverage',
coverageReporters: ['html', 'text']

В итоге отчёты сохранятся как текст и страницa в формате HTML.

Лучшие практики организации тестовой среды и структуры файлов

Где хранить тесты

Есть два подхода:

  1. В отдельной папке (например, tests/) — тесты полностью отделены от исходников.
  2. Вместе с исходниками — файлы тестов лежат рядом, обычно с суффиксом .test.ts или .spec.ts.

Оба способа допустимы, главное — чтобы структура была однородна по всему проекту.

Рекомендации по написанию тестов

  • Пишите осмысленные имена для блоков describe и it.
  • Тестируйте один сценарий за тест.
  • Не полагайтесь на внутренние детали реализации — тестируйте только публичные API ваших функций или классов.
  • Используйте моки только там, где иначе не обойтись.
  • Следите за чистотой после теста (воспользуйтесь Jest hooks: beforeEach, afterEach, beforeAll, afterAll).

Использование хуков

Вот пример с hook-ами:

describe('SomeModule', () => {
  let resource: SomeType

  beforeEach(() => {
    // Инициализация перед каждым тестом
    resource = initializeResource()
  })

  afterEach(() => {
    // Очистка после каждого теста
    cleanup(resource)
  })

  it('should behave as expected', () => {
    // Тест, использующий подготовленный ресурс
    expect(resource.state).toBe('expected')
  })
})
  • beforeEach и afterEach удобны для инициализации/очистки.
  • beforeAll/afterAll — для операций перед/после всех тестов в блоке.

Полезные возможности Jest в проектах на TypeScript

Проверка на исключения

Jest умеет проверять выброс ошибок через .toThrow:

function throwIfNegative(n: number) {
  if (n < 0) {
    throw new Error('negative number')
  }
  return n
}

test('выбрасывает ошибку на отрицательном числе', () => {
  expect(() => throwIfNegative(-1)).toThrow('negative number')
})

Асинхронные тесты

TypeScript отлично дружит с async/await в тестах:

async function asyncAction(): Promise<string> {
  return Promise.resolve('foo')
}

test('асинхронный тест', async () => {
  const result = await asyncAction()
  expect(result).toBe('foo')
})

Либо через возвращение промиса:

test('тест возвращает промис', () => {
  return expect(Promise.resolve(42)).resolves.toBe(42)
})

Параметризованные тесты

Jest поддерживает параметризованные тесты с использованием test.each:

test.each([
  [1, 2, 3],
  [0, 0, 0],
  [-1, -2, -3],
])('сумма %i и %i равна %i', (a, b, expected) => {
  expect(sum(a, b)).toBe(expected)
})

Этот подход помогает быстро добавить сразу несколько кейсов без дублирования кода.

Интеграция Jest с VSCode и другие инструменты

Если вы работаете в VSCode, обратите внимание на расширения:

  • Jest — мгновенно показывает статус тестов прямо в редакторе.
  • Error Lens — помогает быстрее видеть ошибки.

Также можно настраивать автоформаттеры (например, Prettier) и линтеры (ESLint) совместно с Jest.

Заключение

Jest — современный инструмент, который позволяет удобно и быстро тестировать приложения на TypeScript. Благодаря ts-jest вы легко соединяете типизацию TypeScript с мощью тестирования Jest. В этом руководстве вы получили базовые и продвинутые примеры настройки, создания тестов, мокирования, покрытия кода и организации тестовой среды. Применяйте эти рекомендации — и ваш код станет не просто рабочим, а по-настоящему надёжным.

Частозадаваемые технические вопросы по теме статьи и ответы на них

  1. Как ускорить запуск тестов с Jest в больших TypeScript-проектах?

    Чтобы ускорить запуск, используйте опцию isolatedModules: true в ts-jest (если не нуждаетесь в TS-фичах типа const enums). Запускайте jest --runInBand для последовательного выполнения или разделяйте тесты по воркерам. Можно также исключить папки через параметр testPathIgnorePatterns в конфиге.

  2. Почему Jest не видит файл с тестами?

    Проверьте свойство testMatch или testRegex в jest.config.ts. Убедитесь, что путь к файлам тестов и расширения (например, .test.ts) совпадают с шаблоном, указанным в конфигурации.

  3. Мои импорты TypeScript некорректно разрешаются в тестах — как исправить?

    Проверьте, совпадают ли настройки paths в tsconfig.json с тем, как устроены импорты. Для алиасов используйте пакет moduleNameMapper в настройках Jest (например, "^@utils/(.*)$": "<rootDir>/src/utils/$1").

  4. Тесты, зависящие от времени или дат, работают нестабильно — решение?

    Используйте jest.useFakeTimers() в начале тестов и управляйте таймерами вручную функциями jest.runAllTimers(), jest.advanceTimersByTime() и т.д. Для даты удобно мокировать глобальный объект Date.

  5. Можно ли использовать Babel вместо ts-jest?

    Да, Jest поддерживает Babel, но тогда теряется часть проверок TS на этапе компиляции. Для максимальной типобезопасности в тестах используйте ts-jest; Babel хорош для проектов, где важна скорость трансформации и нет сложных TS-фич.

Стрелочка влевоОператор типа keyof в TypeScriptТипы доступа по индексу в TypeScriptСтрелочка вправо

Постройте личный план изучения Typescript до уровня Middle — бесплатно!

Typescript — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Typescript

Открыть базу знаний

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

CSS Flexbox

Антон Ларичев
Гарантия
Бонусы
иконка звёздочки рейтинга4.9
бесплатно
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее