Введение: Зачем тестировать 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