Тестирование в TypeScript: unit, integration, e2e от А до Я

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

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

Начать курс

Введение: Почему тестирование в TypeScript — это must have

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

В этой статье мы разберём три основных уровня тестирования в TypeScript: unit (модульное), integration (интеграционное) и e2e (сквозное). Вы узнаете, чем они отличаются, когда какой применять, и увидите реальные примеры кода. После прочтения вы сможете настроить полноценный тестовый пайплайн для вашего TypeScript-проекта.

1. Unit-тестирование: проверяем атомарные единицы кода

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

1.1 Выбор инструмента и настройка

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

// Установка
npm install --save-dev jest @types/jest ts-jest

// Инициализация конфигурации
npx ts-jest config:init

После этого в корне появится файл jest.config.js с настройками.

1.2 Пример unit-теста для функции

Допустим, у нас есть простая функция для расчёта скидки:

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

Напишем для неё unit-тест:

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

describe('applyDiscount', () => {
  it('должна корректно считать скидку 10%', () => {
    expect(applyDiscount(1000, 10)).toBe(900);
  });

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

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

Запускаем: npx jest. Все три теста должны пройти зелёными.

1.3 Моки и стабы

Если функция обращается к внешнему сервису (например, базе данных), мы заменяем его моком:

// userService.test.ts
import { getUser } from './userService';
import { db } from './database';

jest.mock('./database'); // Автоматический mock

test('getUser возвращает пользователя по ID', async () => {
  (db.findUser as jest.Mock).mockResolvedValue({ id: 1, name: 'Иван' });
  const user = await getUser(1);
  expect(user.name).toBe('Иван');
});

2. Integration-тестирование: проверяем взаимодействие модулей

Integration-тесты проверяют, как разные части системы работают вместе: контроллеры, сервисы, репозитории, базы данных. Они медленнее unit-тестов, но дают больше уверенности в корректности связок.

2.1 Тестируем API-эндпоинт с Express

Для тестирования HTTP-запросов используем библиотеку Supertest вместе с Jest.

// Установка
npm install --save-dev supertest @types/supertest

Предположим, у нас есть Express-приложение:

// app.ts
import express from 'express';
import { createUser } from './controllers/userController';

export const app = express();
app.use(express.json());
app.post('/api/users', createUser);

Интеграционный тест:

// app.test.ts
import request from 'supertest';
import { app } from './app';
import { UserRepository } from './repositories/userRepository';

// Мокаем репозиторий
jest.mock('./repositories/userRepository');

describe('POST /api/users', () => {
  it('должен создать пользователя и вернуть 201', async () => {
    const mockUser = { id: '1', name: 'Анна', email: 'anna@test.com' };
    (UserRepository.prototype.create as jest.Mock).mockResolvedValue(mockUser);

    const response = await request(app)
      .post('/api/users')
      .send({ name: 'Анна', email: 'anna@test.com' });

    expect(response.status).toBe(201);
    expect(response.body).toEqual(mockUser);
  });

  it('должен вернуть 400 при пустом имени', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({ email: 'test@test.com' }); // без name

    expect(response.status).toBe(400);
  });
});

2.2 Тестирование с реальной базой данных

Иногда полезно протестировать интеграцию с реальной БД (например, SQLite в памяти):

// db.test.ts
import { createConnection, getConnection } from 'typeorm';

beforeAll(async () => {
  await createConnection({
    type: 'sqlite',
    database: ':memory:',
    entities: [User],
    synchronize: true,
  });
});

afterAll(async () => {
  await getConnection().close();
});

test('сохранение и чтение пользователя', async () => {
  const repo = getConnection().getRepository(User);
  const user = repo.create({ name: 'Петр', email: 'petr@test.com' });
  await repo.save(user);
  const found = await repo.findOneBy({ email: 'petr@test.com' });
  expect(found?.name).toBe('Петр');
});

3. E2E-тестирование: проверяем сценарий целиком

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

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

Playwright от Microsoft — современный фреймворк для e2e-тестирования, поддерживающий TypeScript из коробки.

// Установка
npm install --save-dev @                

Блоги

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