Тестирование в TypeScript: Unit, Integration и E2E тесты

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

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

Начать курс

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

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

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

1. Unit-тестирование в TypeScript

Что такое unit-тесты?

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

Инструменты: Jest + ts-jest

Самый популярный фреймворк для unit-тестирования в TypeScript — Jest с транслятором ts-jest. Он поддерживает моки, шпионы, coverage и отлично интегрируется с TypeScript.

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

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

// discount.ts
export function calculateDiscount(price: number, discountPercent: number): number {
  if (price < 0 || discountPercent < 0 || discountPercent > 100) {
    throw new Error('Invalid input');
  }
  return price * (1 - discountPercent / 100);
}

Пишем тест:

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

describe('calculateDiscount', () => {
  it('should apply 10% discount correctly', () => {
    expect(calculateDiscount(1000, 10)).toBe(900);
  });

  it('should throw error for negative price', () => {
    expect(() => calculateDiscount(-100, 10)).toThrow('Invalid input');
  });

  it('should handle 0% discount', () => {
    expect(calculateDiscount(500, 0)).toBe(500);
  });
});

Запуск: npx jest. Jest автоматически найдёт все файлы *.test.ts.

Best practices для unit-тестов

  • Изолируйте зависимости — используйте моки (jest.mock) для модулей, которые обращаются к БД или API.
  • Тестируйте граничные случаи — пустые массивы, null, отрицательные числа.
  • Соблюдайте принцип FIRST — Fast, Isolated, Repeatable, Self-validating, Timely.
  • Используйте describe/it — для читаемой структуры тестов.

2. Интеграционное тестирование (Integration Tests)

Что проверяют интеграционные тесты?

Интеграционные тесты проверяют взаимодействие нескольких модулей: контроллер + сервис + база данных, или API-эндпоинт + middleware. Они медленнее unit-тестов, но дают больше уверенности в корректной связке компонентов.

Инструменты: Supertest + Jest + in-memory БД

Для тестирования Express/Node.js API часто используют Supertest (HTTP-ассершены) в паре с Jest. Базу данных можно поднять in-memory (например, mongodb-memory-server или sqlite3).

Пример интеграционного теста для Express API

Предположим, у нас есть простой REST API для управления пользователями:

// app.ts
import express from 'express';
import { userRouter } from './routes/userRoutes';

const app = express();
app.use(express.json());
app.use('/api/users', userRouter);

export { app };

Тест с Supertest:

// user.integration.test.ts
import request from 'supertest';
import { app } from '../app';

describe('POST /api/users', () => {
  it('should create a new user and return 201', async () => {
    const newUser = { name: 'Alice', email: 'alice@example.com' };
    const response = await request(app)
      .post('/api/users')
      .send(newUser)
      .expect(201);

    expect(response.body).toHaveProperty('id');
    expect(response.body.name).toBe('Alice');
  });

  it('should return 400 if email is missing', async () => {
    await request(app)
      .post('/api/users')
      .send({ name: 'Bob' })
      .expect(400);
  });
});

Настройка окружения

Для изоляции интеграционных тестов рекомендуется:

  • Поднимать тестовую БД перед всеми тестами (beforeAll) и очищать после каждого (afterEach).
  • Использовать dotenv с отдельным .env.test.
  • Отключать внешние сервисы (платежи, email) через моки.

3. End-to-End тестирование (E2E)

Что такое E2E-тесты?

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

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

Playwright — современный фреймворк от Microsoft для автоматизации браузеров (Chromium, Firefox, WebKit). Он поддерживает TypeScript из коробки, умеет ждать элементы, делать скриншоты и работать в headless-режиме.

Пример E2E-теста на Playwright

Предположим, у нас есть страница входа на сайт:

// login.e2e.test.ts
import { test, expect } from '@playwright/test';

test('should successfully log in with valid credentials', async ({ page }) => {
  await page.goto('https://example.com/login');

  await page.fill('#email', 'user@example.com');
  await page.fill('#password', 'correct-password');
  await page.click('button[type="submit"]');

  // Ждём редиректа на дашборд
  await expect(page).toHaveURL(/\/dashboard/);
  await expect(page.locator('.welcome-message')).toContainText('Welcome, User');
});

test('should show error for invalid password', async ({ page }) => {
  await page.goto('https://example.com/login');

  await page.fill('#email', 'user@example.com');
  await page.fill('#password', 'wrong');
  await page                

Блоги

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