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 только при конфликтах ресурсов

Связанные темы

Ресурсы