Тестирование в TypeScript: Unit, Integration, E2E — полное руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Зачем тестировать TypeScript-код?

TypeScript — это мощный инструмент, который добавляет статическую типизацию в JavaScript, позволяя отлавливать ошибки на этапе компиляции. Однако даже самая строгая система типов не гарантирует, что ваш код будет работать правильно в реальных условиях. Логические ошибки, неверная обработка граничных случаев и проблемы интеграции модулей остаются за рамками компилятора. Именно здесь на помощь приходит тестирование.

Тестирование в TypeScript — это не просто проверка функций. Это комплексный процесс, который включает три ключевых уровня: Unit (модульное), Integration (интеграционное) и E2E (сквозное). Каждый из них решает свою задачу и вместе они формируют "пирамиду тестирования", обеспечивающую надежность приложения.

В этой статье мы разберем, как писать тесты для TypeScript на всех трех уровнях, используя современные инструменты: Jest, Supertest и Playwright. Вы узнаете, какие ошибки помогают предотвратить эти тесты и как внедрить их в ваш проект.

Unit-тестирование: Проверка изолированных модулей

Unit-тесты (модульные тесты) — это фундамент пирамиды. Они тестируют отдельные функции, классы или методы в изоляции от внешних зависимостей (базы данных, API, файловой системы). Главная цель — убедиться, что каждый "кирпичик" вашего приложения работает корректно.

Инструменты и настройка

Самый популярный фреймворк для unit-тестирования в TypeScript — Jest. Он предоставляет встроенную поддержку TypeScript через ts-jest или @swc/jest.

Установка Jest в проект:

npm install --save-dev jest @types/jest ts-jest
npx ts-jest config:init

Пример unit-теста

Рассмотрим простую функцию для расчета скидки:

// discount.ts
export function applyDiscount(price: number, discountPercent: number): number {
  if (discountPercent < 0 || discountPercent > 100) {
    throw new Error('Процент скидки должен быть от 0 до 100');
  }
  return price - (price * discountPercent) / 100;
}

Напишем unit-тесты, покрывающие основные сценарии:

// discount.test.ts
import { applyDiscount } from './discount';

describe('applyDiscount', () => {
  it('должен корректно применять скидку 20%', () => {
    expect(applyDiscount(100, 20)).toBe(80);
  });

  it('должен возвращать исходную цену при скидке 0%', () => {
    expect(applyDiscount(50, 0)).toBe(50);
  });

  it('должен возвращать 0 при скидке 100%', () => {
    expect(applyDiscount(200, 100)).toBe(0);
  });

  it('должен выбрасывать ошибку при отрицательной скидке', () => {
    expect(() => applyDiscount(100, -10)).toThrow('Процент скидки должен быть от 0 до 100');
  });

  it('должен выбрасывать ошибку при скидке больше 100%', () => {
    expect(() => applyDiscount(100, 150)).toThrow('Процент скидки должен быть от 0 до 100');
  });
});

Важно: Unit-тесты не должны обращаться к внешним ресурсам. Вместо этого используйте моки (mocks) для подмены зависимостей, например, с помощью jest.mock().

Интеграционное тестирование: Взаимодействие между модулями

Интеграционные тесты проверяют, как разные части системы работают вместе. Это может быть взаимодействие между сервисом и базой данных, контроллером и репозиторием, или между микросервисами. В отличие от unit-тестов, здесь мы не изолируем компоненты полностью.

Пример: тестирование REST API

Предположим, у нас есть Express-приложение с одним эндпоинтом. Для тестирования HTTP-запросов используем Supertest.

// userController.ts
import { Router, Request, Response } from 'express';

interface User {
  id: number;
  name: string;
}

const users: User[] = [{ id: 1, name: 'Alice' }];

const router = Router();

router.get('/users', (req: Request, res: Response) => {
  res.json(users);
});

router.post('/users', (req: Request, res: Response) => {
  const newUser: User = { id: users.length + 1, name: req.body.name };
  users.push(newUser);
  res.status(201).json(newUser);
});

export default router;

Напишем интеграционный тест, который проверяет полный сценарий "создание + получение":

// userController.test.ts
import request from 'supertest';
import express from 'express';
import userRouter from './userController';

const app = express();
app.use(express.json());
app.use(userRouter);

describe('User API', () => {
  it('GET /users — должен возвращать список пользователей', async () => {
    const res = await request(app).get('/users');
    expect(res.status).toBe(200);
    expect(res.body).toEqual([{ id: 1, name: 'Alice' }]);
  });

  it('POST /users — должен создавать нового пользователя', async () => {
    const newUser = { name: 'Bob' };
    const res = await request(app).post('/users').send(newUser);
    expect(res.status).toBe(201);
    expect(res.body).toMatchObject({ name: 'Bob' });
    expect(res.body.id).toBeDefined();
  });
});

Совет: Для тестов, работающих с базой данных, используйте in-memory базу (например, SQLite) или контейнеры Docker (через библиотеку testcontainers), чтобы не портить реальные данные.

E2E-тестирование: Сквозная проверка пользовательского сценария

End-to-End (E2E) тесты — это вершина пирамиды. Они симулируют действия реального пользователя: открытие браузера, клики, ввод текста, переходы между страницами. E2E-тесты проверяют, что вся система целиком (фронтенд, бэкенд, база данных) работает корректно.

Инструменты: Playwright

Один из лучших инструментов для E2E — Playwright от Microsoft. Он поддерживает TypeScript "из коробки" и умеет работать с Chrome, Firefox и Safari.

Установка:

npm install --save-dev @playwright/test
npx playwright install

Пример E2E-теста для ве

Блоги

Рекомендаций по книгам