Integration тесты
Integration-тест проверяет взаимодействие нескольких модулей системы вместе — например, роутер + сервис + база данных — без полного запуска браузера или UI.
Зачем нужно
Unit-тесты проверяют каждый компонент в изоляции, но не гарантируют, что компоненты правильно работают вместе. Integration-тесты закрывают этот пробел: они обнаруживают ошибки на стыке модулей — некорректные SQL-запросы, несоответствие контрактов API, проблемы с маршалингом данных. Это наиболее практичный уровень для тестирования REST API.
Где используется
- Тестирование HTTP-эндпоинтов Express/Fastify/Koa с реальной (или in-memory) БД
- Проверка цепочки: валидация → бизнес-логика → сохранение → ответ
- Тестирование очередей сообщений, событий EventEmitter
- Проверка взаимодействия сервисов внутри монолита перед выделением в микросервисы
Основной контент
Тестирование Express API с supertest
npm install --save-dev supertest
// app.js
const express = require('express');
const app = express;
app.use(express.json());
app.get('/users/:id', async (req, res) => {
const user = await db.findUser(req.params.id);
if (!user) return res.status(404).json({ error: 'Not found' });
res.json(user);
});
app.post('/users', async (req, res) => {
const { name, email } = req.body;
if (!name || !email) return res.status(400).json({ error: 'Bad request' });
const user = await db.createUser({ name, email });
res.status(201).json(user);
});
module.exports = app;
// app.test.js
const request = require('supertest');
const app = require('./app');
const db = require('./db');
beforeAll(async () => {
await db.connect; // реальная тестовая БД или in-memory SQLite
});
afterAll(async () => {
await db.disconnect();
});
afterEach(async () => {
await db.clearUsers; // очистка между тестами
});
describe('GET /users/:id', () => {
test('возвращает пользователя по ID', async () => {
const created = await db.createUser({ name: 'Alice', email: 'a@test.com' });
const res = await request(app).get(`/users/${created.id}`);
expect(res.status).toBe(200);
expect(res.body).toMatchObject({ name: 'Alice', email: 'a@test.com' });
});
test('возвращает 404 для несуществующего ID', async () => {
const res = await request(app).get('/users/99999');
expect(res.status).toBe(404);
expect(res.body.error).toBe('Not found');
});
});
describe('POST /users', () => {
test('создаёт пользователя с корректными данными', async () => {
const res = await request(app)
.post('/users')
.send({ name: 'Bob', email: 'bob@test.com' });
expect(res.status).toBe(201);
expect(res.body.id).toBeDefined();
});
test('возвращает 400 если email отсутствует', async () => {
const res = await request(app).post('/users').send({ name: 'Bob' });
expect(res.status).toBe(400);
});
});
Паттерн: реальная БД vs in-memory
// Для SQLite in-memory (быстро, изолированно)
const Database = require('better-sqlite3');
const db = new Database(':memory:');
// Для PostgreSQL — использовать тестовую БД через env-переменную
const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.TEST_DATABASE_URL });
Частичное мокирование — только внешние сервисы
// Замокируем только внешний платёжный API, остальное реальное
jest.mock('./paymentGateway', () => ({
charge: jest.fn.mockResolvedValue({ success: true, transactionId: 'txn_123' }),
}));
test('создаёт заказ и списывает оплату', async () => {
const res = await request(app)
.post('/orders')
.send({ productId: 1, amount: 500 });
expect(res.status).toBe(201);
expect(res.body.transactionId).toBe('txn_123');
// Проверяем что заказ сохранился в БД
const order = await db.findOrder(res.body.id);
expect(order.status).toBe('paid');
});
Частые ошибки
- Не очищать БД между тестами — данные от одного теста влияют на следующий; используй
afterEachс очисткой таблиц - Использовать production-БД — integration-тесты должны запускаться на изолированной тестовой БД или in-memory хранилище
- Мокировать слишком много — если замокированы и БД, и сервис, и репозиторий — это уже не integration-тест; смысл теряется
- Не проверять статус код —
res.body.nameможет совпасть случайно; всегда проверяй иres.status, и тело ответа - Медленные тесты без параллелизма — в Jest можно запускать каждый тест-файл в отдельном worker; используй
--runInBandтолько при конфликтах ресурсов