Интеграционное тестирование JavaScript с Docker: от А до Я
В современной веб-разработке качество кода — это не просто метрика, а залог стабильности продукта. Интеграционное тестирование (Integration Testing) занимает ключевое место в пирамиде тестирования, позволяя проверить взаимодействие между различными модулями, базами данных, внешними API и сервисами. Однако настройка изолированных и воспроизводимых сред для таких тестов часто становится головной болью. Здесь на помощь приходит Docker — инструмент контейнеризации, который идеально подходит для создания чистых, повторяемых тестовых окружений.
В этой статье мы разберём, как организовать интеграционное тестирование для JavaScript (Node.js) приложений с использованием Docker. Вы узнаете, как поднять тестовую базу данных, запустить зависимости в контейнерах и написать эффективные тесты, которые можно легко интегрировать в пайплайн CI/CD.
1. Почему Docker идеален для интеграционных тестов?
Традиционный подход к интеграционному тестированию часто требует ручной настройки окружения на каждой машине разработчика или сервере CI. Это приводит к проблеме "на моей машине работает". Docker решает эту проблему кардинально:
- Изоляция: Каждый тест запускается в чистом контейнере, что исключает влияние побочных эффектов от предыдущих запусков.
- Воспроизводимость: Одна и та же среда (версия Node.js, базы данных, системные библиотеки) гарантированно работает везде: на локальной машине разработчика, на сервере сборки и в продакшене.
- Управление зависимостями: Вы можете легко поднять сложные инфраструктуры (Redis, PostgreSQL, RabbitMQ) одной командой
docker-compose up -d. - Скорость: Контейнеры запускаются за секунды, в отличие от виртуальных машин. Docker кэширует слои, что ускоряет повторные сборки.
В контексте Node.js, Docker позволяет "замокать" внешние сервисы, не покидая среды разработки. Вместо того чтобы стучаться в продакшен-API, вы поднимаете тестовый контейнер с, например, json-server или testcontainers.
2. Настройка проекта Node.js для тестирования
Предположим, у нас есть простое Express-приложение, которое сохраняет пользователей в PostgreSQL. Наша задача — протестировать эндпоинт POST /users.
Структура проекта:
project/ ├── src/ │ ├── app.js │ ├── db.js │ └── routes/ │ └── users.js ├── test/ │ └── integration/ │ └── users.test.js ├── docker-compose.test.yml ├── Dockerfile ├── package.json └── .env.test
Для тестов мы будем использовать популярный стек: Mocha (фреймворк) + Chai (библиотека утверждений) + Supertest (HTTP-тестирование).
Установим зависимости:
npm install --save-dev mocha chai supertest
Файл package.json должен содержать скрипт для запуска тестов:
{
"scripts": {
"test:integration": "mocha test/integration/**/*.test.js --timeout 10000"
}
}
3. Создание Docker-окружения для тестов
Ключевой файл — docker-compose.test.yml. Он описывает сервисы, необходимые для тестов.
version: '3.8'
services:
app-test:
build:
context: .
dockerfile: Dockerfile
environment:
- NODE_ENV=test
- DB_HOST=postgres-test
- DB_PORT=5432
- DB_USER=test_user
- DB_PASSWORD=test_pass
- DB_NAME=test_db
depends_on:
postgres-test:
condition: service_healthy
command: ["npm", "run", "test:integration"]
postgres-test:
image: postgres:15-alpine
environment:
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_pass
POSTGRES_DB: test_db
ports:
- "5433:5432" # Пробрасываем на другой порт, чтобы не конфликтовать с локальной БД
healthcheck:
test: ["CMD-SHELL", "pg_isready -U test_user -d test_db"]
interval: 5s
timeout: 5s
retries: 5
Обратите внимание на healthcheck. Мы не можем запускать тесты, пока PostgreSQL не будет готова принимать соединения. Docker Compose ждет, пока контейнер с БД не станет "здоровым".
Dockerfile для приложения:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD ["node", "src/app.js"]
4. Написание интеграционных тестов
Теперь самое интересное — код теста. Мы используем паттерн setup/teardown для управления состоянием базы данных.
Файл: test/integration/users.test.js
const request = require('supertest');
const { expect } = require('chai');
const app = require('../../src/app');
const { initDb, closeDb, clearDb } = require('../../src/db');
describe('POST /users', () => {
before(async () => {
// Подключаемся к тестовой БД (настройки берутся из переменных окружения Docker)
await initDb();
});
after(async () => {
// Закрываем соединение после всех тестов
await closeDb();
});
beforeEach(async () => {
// Очищаем таблицу перед каждым тестом для изоляции
await clearDb('users');
});
it('должен создать нового пользователя и вернуть статус 201', async () => {
const newUser = {
name: 'Иван Иванов',
email: 'ivan@example.com'
};
const response = await request(app)
.post('/users')
.send(newUser)
.expect('Content-Type', /json/)
.expect(201);
// Проверяем структуру ответа
expect(response.body).to.have.property('id');
expect(response.body.name).to.equal(newUser.name);
expect(response.body.email).to.equal(newUser.email);
});
it('должен вернуть ошибку 400 при отсутствии email', async () => {
const invalidUser = { name: 'Петр Петров' };
const response = await request(app)
.post('/users')
.send(invalidUser)