23. Integration тесты

Что такое Integration тесты?
Заголовок раздела «Что такое Integration тесты?»Integration тесты проверяют взаимодействие между несколькими модулями, компонентами или сервисами — в отличие от unit тестов, которые проверяют всё изолированно.
Unit Test: [Module A] ← тестируем изолированноIntegration: [Module A] → [Module B] → [Database] ↑ тестируем всю цепочкуТестирование Express/Fastify API
Заголовок раздела «Тестирование Express/Fastify API»npm install --save-dev supertestimport express from 'express';import { pool } from './db';
const app = express();app.use(express.json());
app.get('/api/users', async (req, res) => { const { rows } = await pool.query('SELECT id, name, email FROM users ORDER BY name'); res.json(rows);});
app.post('/api/users', async (req, res) => { const { name, email } = req.body; if (!name || !email) return res.status(400).json({ error: 'Name and email required' });
try { const { rows } = await pool.query( 'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *', [name, email] ); res.status(201).json(rows[0]); } catch (err) { if (err.code === '23505') { return res.status(409).json({ error: 'Email already exists' }); } throw err; }});
export default app;import request from 'supertest';import app from './app';import { pool } from './db';
describe('Users API', () => { beforeEach(async () => { await pool.query('DELETE FROM users'); });
afterAll(async () => { await pool.end(); });
describe('GET /api/users', () => { test('returns empty array when no users', async () => { const res = await request(app).get('/api/users').expect(200); expect(res.body).toEqual([]); });
test('returns all users sorted by name', async () => {
const res = await request(app).get('/api/users').expect(200);
expect(res.body).toHaveLength(2); expect(res.body[0].name).toBe('Alice'); expect(res.body[1].name).toBe('Bob'); }); });
describe('POST /api/users', () => { test('creates user with valid data', async () => { const res = await request(app) .post('/api/users') .expect(201);
expect(res.body).toMatchObject({ id: expect.any(Number), name: 'Alice', });
// Проверяем в БД const { rows } = await pool.query('SELECT * FROM users'); expect(rows).toHaveLength(1); });
test('returns 400 for missing fields', async () => { await request(app) .post('/api/users') .send({ name: 'Alice' }) // нет email .expect(400); });
test('returns 409 for duplicate email', async () => {
const res = await request(app) .post('/api/users') .expect(409);
expect(res.body.error).toBe('Email already exists'); }); });});Тест-база данных
Заголовок раздела «Тест-база данных»import { Pool } from 'pg';
export const pool = new Pool({ connectionString: process.env.DATABASE_URL || 'postgres://localhost/myapp_test',});module.exports = { globalSetup: './tests/global-setup.ts', globalTeardown: './tests/global-teardown.ts',};
// tests/global-setup.tsexport default async function() { // Создать тестовую БД или миграции const pool = new Pool({ connectionString: 'postgres://localhost/myapp_test' }); await pool.query(` CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, created_at TIMESTAMP DEFAULT NOW() ) `); await pool.end();}Docker Compose для тестов
Заголовок раздела «Docker Compose для тестов»services: db: image: postgres:16-alpine environment: POSTGRES_DB: myapp_test POSTGRES_PASSWORD: test ports: ["5433:5432"] tmpfs: /var/lib/postgresql/data # RAM — быстрее!docker compose -f docker-compose.test.yml up -dDATABASE_URL=postgres://postgres:test@localhost:5433/myapp_test npm testdocker compose -f docker-compose.test.yml downТестирование с Prisma
Заголовок раздела «Тестирование с Prisma»import { PrismaClient } from '@prisma/client';import { execSync } from 'child_process';
const prisma = new PrismaClient();
beforeAll(async () => { // Применить миграции к тестовой БД execSync('npx prisma migrate deploy', { env: { ...process.env, DATABASE_URL: process.env.TEST_DATABASE_URL }, });});
beforeEach(async () => { // Очистить все таблицы const tables = await prisma.$queryRaw` SELECT tablename FROM pg_tables WHERE schemaname = 'public' `; for (const { tablename } of tables) { await prisma.$executeRawUnsafe(`TRUNCATE "${tablename}" CASCADE`); }});
afterAll(async () => { await prisma.$disconnect();});Тестирование межсервисной интеграции
Заголовок раздела «Тестирование межсервисной интеграции»// Тест: OrderService + PaymentService + EmailServicedescribe('Order Flow', () => { test('completes order with payment and notification', async () => { // Arrange const user = await createTestUser(); const product = await createTestProduct({ price: 100, stock: 5 });
// Act const order = await orderService.createOrder({ userId: user.id, items: [{ productId: product.id, qty: 2 }], });
// Assert — заказ создан expect(order.status).toBe('completed'); expect(order.total).toBe(200);
// Assert — сток уменьшился const updatedProduct = await productService.getById(product.id); expect(updatedProduct.stock).toBe(3);
// Assert — платёж проведён const payment = await paymentService.getByOrderId(order.id); expect(payment.status).toBe('captured'); expect(payment.amount).toBe(200); });});Практические задания
Заголовок раздела «Практические задания»- Напиши integration тест для REST API с тестовой БД (PostgreSQL в Docker)
- Протестируй сценарий: регистрация → логин → получение профиля
- Настрой Docker Compose для автоматического поднятия тест-окружения
- Integration тесты проверяют реальное взаимодействие модулей
- supertest — стандарт для тестирования HTTP API
- Тестовая БД в Docker (tmpfs) — быстро и изолированно
- beforeEach → очистка данных между тестами