Дмитрий Кулаков
Руководство по тестированию 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.
Лучшие практики организации тестовой среды и структуры файлов
Где хранить тесты
Есть два подхода:
- В отдельной папке (например,
tests/
) — тесты полностью отделены от исходников. - Вместе с исходниками — файлы тестов лежат рядом, обычно с суффиксом
.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. В этом руководстве вы получили базовые и продвинутые примеры настройки, создания тестов, мокирования, покрытия кода и организации тестовой среды. Применяйте эти рекомендации — и ваш код станет не просто рабочим, а по-настоящему надёжным.
Частозадаваемые технические вопросы по теме статьи и ответы на них
Как ускорить запуск тестов с Jest в больших TypeScript-проектах?
Чтобы ускорить запуск, используйте опцию
isolatedModules: true
вts-jest
(если не нуждаетесь в TS-фичах типа const enums). Запускайтеjest --runInBand
для последовательного выполнения или разделяйте тесты по воркерам. Можно также исключить папки через параметрtestPathIgnorePatterns
в конфиге.Почему Jest не видит файл с тестами?
Проверьте свойство
testMatch
илиtestRegex
вjest.config.ts
. Убедитесь, что путь к файлам тестов и расширения (например,.test.ts
) совпадают с шаблоном, указанным в конфигурации.Мои импорты TypeScript некорректно разрешаются в тестах — как исправить?
Проверьте, совпадают ли настройки
paths
вtsconfig.json
с тем, как устроены импорты. Для алиасов используйте пакетmoduleNameMapper
в настройках Jest (например,"^@utils/(.*)$": "<rootDir>/src/utils/$1"
).Тесты, зависящие от времени или дат, работают нестабильно — решение?
Используйте
jest.useFakeTimers()
в начале тестов и управляйте таймерами вручную функциямиjest.runAllTimers()
,jest.advanceTimersByTime()
и т.д. Для даты удобно мокировать глобальный объектDate
.Можно ли использовать Babel вместо ts-jest?
Да, Jest поддерживает Babel, но тогда теряется часть проверок TS на этапе компиляции. Для максимальной типобезопасности в тестах используйте ts-jest; Babel хорош для проектов, где важна скорость трансформации и нет сложных TS-фич.
Постройте личный план изучения Typescript до уровня Middle — бесплатно!
Typescript — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Typescript
Лучшие курсы по теме

TypeScript с нуля
Антон Ларичев
CSS Flexbox
Антон Ларичев