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

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

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

Начать курс

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

Интеграционное тестирование — ключевой этап разработки современных веб-приложений. Оно позволяет проверить взаимодействие между различными компонентами системы: базой данных, кэшем, внешними API и самим приложением. Docker делает этот процесс предсказуемым, изолированным и воспроизводимым. В этой статье мы рассмотрим продвинутые техники интеграционного тестирования JavaScript (Node.js) с использованием Docker.

Зачем Docker в интеграционном тестировании?

Традиционное интеграционное тестирование сталкивается с проблемами:

  • Различия в окружениях разработчика, CI и продакшена.
  • Загрязнение данных в общих базах данных.
  • Сложность запуска зависимостей (Redis, PostgreSQL, Kafka).

Docker решает эти проблемы, предоставляя легковесные, изолированные контейнеры. Каждый тест может запускать собственную инфраструктуру, уничтожая её после завершения. Это гарантирует чистоту и повторяемость результатов.

Настройка тестового окружения с Docker Compose

Для продвинутого тестирования мы будем использовать docker-compose.yml, который описывает все сервисы, необходимые для тестов: само приложение, базу данных, кэш и mock-серверы.

Пример docker-compose.test.yml:

version: '3.8'
services:
  app:
    build: .
    environment:
      - NODE_ENV=test
      - DB_HOST=postgres_test
      - REDIS_HOST=redis_test
      - MOCK_SERVER_URL=http://mock-server:8080
    depends_on:
      - postgres_test
      - redis_test
      - mock-server
    command: ["npm", "run", "test:integration"]

  postgres_test:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: testdb
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
    tmpfs: /var/lib/postgresql/data

  redis_test:
    image: redis:7-alpine
    tmpfs: /data

  mock-server:
    image: mockserver/mockserver:latest
    environment:
      MOCKSERVER_LOG_LEVEL: ERROR

Ключевые моменты:

  • tmpfs: данные БД и Redis хранятся в оперативной памяти, что ускоряет тесты.
  • mock-server: изолированный сервис для имитации внешних API.
  • NODE_ENV=test: переключает приложение на тестовую конфигурацию.

Написание интеграционных тестов с Testcontainers

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

Установка:

npm install --save-dev testcontainers

Пример теста с PostgreSQL:

const { GenericContainer } = require('testcontainers');
const { Client } = require('pg');

describe('User Repository Integration Test', () => {
  let container;
  let client;

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

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

    // Создаём схему
    await client.query(`
      CREATE TABLE users (
        id SERIAL PRIMARY KEY,
        name VARCHAR(100),
        email VARCHAR(100) UNIQUE
      )
    `);
  });

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

  test('should insert and retrieve user', async () => {
    await client.query(
      'INSERT INTO users (name, email) VALUES ($1, $2)',
      ['John Doe', 'john@example.com']
    );

    const result = await client.query(
      'SELECT * FROM users WHERE email = $1',
      ['john@example.com']
    );

    expect(result.rows[0].name).toBe('John Doe');
  });
});

Преимущества Testcontainers:

  • Контейнеры живут ровно столько, сколько длится тест.
  • Порты динамические — нет конфликтов.
  • Можно запускать разные версии БД для разных тестов.

Тестирование с mock-сервисами и ожиданиями

В реальных приложениях часто есть внешние HTTP-зависимости. Используем MockServer для имитации их поведения.

Пример настройки mock-сервера в тесте:

const { GenericContainer } = require('testcontainers');
const axios = require('axios');

describe('External API Integration', () => {
  let mockContainer;
  let mockClient;

  beforeAll(async () => {
    // Запускаем MockServer
    mockContainer = await new GenericContainer('mockserver/mockserver:latest')
      .withExposedPorts(1080)
      .start();

    const baseUrl = `http://${mockContainer.getHost()}:${mockContainer.getMappedPort(1080)}`;
    mockClient = axios.create({ baseURL: baseUrl });

    // Настраиваем ожидание (expectation)
    await mockClient.put('/mockserver/expectation', {
      httpRequest: {
        method: 'GET',
        path: '/api/users/1'
      },
      httpResponse: {
        statusCode: 200,
        body: { id: 1, name: 'Mock User' }
      }
    });
  });

  afterAll(async () => {
    await mockContainer.stop();
  });

  test('should return mocked user data', async () => {
    const response = await axios.get(`${mockContainer.getHost()}:${mockContainer.getMappedPort(1080)}/api/users/1`);
    expect(response.status).toBe(200);
    expect(response.data.name).toBe('Mock User');
  });
});

Расширенные возможности MockServer:

  • Верификация вызовов: проверьте, сколько раз был вызван эндпоинт.
  • Динамические ответы: возвращайте разные данные в зависимости от тела запроса.
  • Задержки и ошибки: симулируйте таймауты и 500 ошибки.

CI/CD интеграция и параллельный запуск

Для продакшн-пайплайна важно запускать тесты быстро. Используем GitHub Actions с Docker Compose и параллелизацией.

Пример .github/work

Блоги

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