Интеграционное тестирование TypeScript с Docker: продвинутый уровень

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

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

Начать курс

Интеграционное тестирование TypeScript с Docker: продвинутый уровень

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

1. Почему Docker для интеграционного тестирования TypeScript?

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

  • Изоляция: каждый тест запускается в чистом окружении
  • Скорость: контейнеры запускаются за секунды
  • Параллелизм: можно одновременно тестировать разные версии зависимостей

2. Настройка Docker Compose для тестовой среды

Создадим docker-compose.test.yml, который поднимает PostgreSQL, Redis и само приложение для тестов:

version: '3.8'
services:
  db-test:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: testdb
      POSTGRES_USER: test
      POSTGRES_PASSWORD: testpass
    ports:
      - "5433:5432"
    tmpfs: /var/lib/postgresql/data

  redis-test:
    image: redis:7-alpine
    ports:
      - "6380:6379"

  app-test:
    build:
      context: .
      dockerfile: Dockerfile.test
    environment:
      DATABASE_URL: postgres://test:testpass@db-test:5432/testdb
      REDIS_URL: redis://redis-test:6379
    depends_on:
      - db-test
      - redis-test
    command: ["npm", "run", "test:integration"]

Здесь мы используем tmpfs для базы данных, чтобы данные не сохранялись между запусками, что ускоряет тесты.

3. Продвинутая настройка TypeScript-тестов с TestContainers

Библиотека testcontainers позволяет управлять контейнерами прямо из кода TypeScript. Это даёт гибкость: вы можете поднимать контейнеры только для определённых тестовых сценариев.

Установка:

npm install --save-dev testcontainers

Пример интеграционного теста с динамическим контейнером:

import { GenericContainer, StartedTestContainer } from 'testcontainers';
import { Client } from 'pg';
import { UserService } from './user-service';

let container: StartedTestContainer;
let client: Client;

beforeAll(async () => {
  // Запускаем PostgreSQL контейнер
  container = await new GenericContainer('postgres:15-alpine')
    .withEnvironment({
      POSTGRES_DB: 'test',
      POSTGRES_USER: 'test',
      POSTGRES_PASSWORD: 'test'
    })
    .withExposedPorts(5432)
    .start();

  // Подключаемся к тестовой БД
  const host = container.getHost();
  const port = container.getMappedPort(5432);
  client = new Client({
    host,
    port,
    database: 'test',
    user: 'test',
    password: 'test'
  });
  await client.connect();

  // Выполняем миграции
  await client.query(`
    CREATE TABLE users (
      id SERIAL PRIMARY KEY,
      email VARCHAR(255) UNIQUE NOT NULL,
      name VARCHAR(255)
    )
  `);
});

afterAll(async () => {
  await client.end();
  await container.stop();
});

test('UserService.createUser создаёт пользователя в БД', async () => {
  const userService = new UserService(client);
  const user = await userService.createUser({
    email: 'test@example.com',
    name: 'Test User'
  });

  const result = await client.query('SELECT * FROM users WHERE email = $1', ['test@example.com']);
  expect(result.rows[0]).toMatchObject({
    email: 'test@example.com',
    name: 'Test User'
  });
});

Такой подход позволяет писать тесты, которые полностью изолированы и не зависят от внешней инфраструктуры.

4. Параллельный запуск тестов и управление ресурсами

При большом количестве тестов важно оптимизировать использование Docker. Используйте --parallel в Jest и настройте пул контейнеров:

// jest.config.ts
export default {
  preset: 'ts-jest',
  testEnvironment: 'node',
  testMatch: ['**/*.integration.test.ts'],
  maxWorkers: 4,
  globalSetup: './test/global-setup.ts',
  globalTeardown: './test/global-teardown.ts'
};

Пример глобальной настройки для переиспользования контейнеров:

// test/global-setup.ts
import { GenericContainer } from 'testcontainers';

export default async function setup() {
  const dbContainer = await new GenericContainer('postgres:15-alpine')
    .withEnvironment({
      POSTGRES_DB: 'test',
      POSTGRES_USER: 'test',
      POSTGRES_PASSWORD: 'test'
    })
    .withExposedPorts(5432)
    .start();

  process.env.DATABASE_HOST = dbContainer.getHost();
  process.env.DATABASE_PORT = String(dbContainer.getMappedPort(5432));
  (global as any).__DB_CONTAINER__ = dbContainer;
}

Этот подход сокращает время выполнения тестов до 50% за счёт повторного использования контейнеров.

5. Интеграция с CI/CD и обработка ошибок

В пайплайнах GitHub Actions или GitLab CI используйте Docker Compose для запуска тестов:

# .github/workflows/test.yml
name: Integration Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15-alpine
        env:
          POSTGRES_DB: test
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
        ports:
          - 5432:5432
      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run test:integration
        env:
          DATABASE_URL: postgres://test:test@localhost:5432/test
          REDIS_URL: redis://localhost:6379

Важно добавить таймауты и повторные попытки для тестов, работающих с сетью:

test('API должен ответить в течение 5 секунд', async () => {
  const response = await fetch('                

Блоги

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