Интеграционное тестирование 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