Перейти к содержимому

23. Integration тесты

Integration Tests

Integration тесты проверяют взаимодействие между несколькими модулями, компонентами или сервисами — в отличие от unit тестов, которые проверяют всё изолированно.

Unit Test: [Module A] ← тестируем изолированно
Integration: [Module A] → [Module B] → [Database]
↑ тестируем всю цепочку
Окно терминала
npm install --save-dev supertest
app.ts
import 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;
app.test.ts
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 () => {
await pool.query("INSERT INTO users (name, email) VALUES ('Bob', '[email protected]')");
await pool.query("INSERT INTO users (name, email) VALUES ('Alice', '[email protected]')");
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')
.send({ name: 'Alice', email: '[email protected]' })
.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 () => {
await request(app).post('/api/users').send({ name: 'Alice', email: '[email protected]' });
const res = await request(app)
.post('/api/users')
.send({ name: 'Bob', email: '[email protected]' })
.expect(409);
expect(res.body.error).toBe('Email already exists');
});
});
});
db.ts
import { Pool } from 'pg';
export const pool = new Pool({
connectionString: process.env.DATABASE_URL || 'postgres://localhost/myapp_test',
});
jest.config.js
module.exports = {
globalSetup: './tests/global-setup.ts',
globalTeardown: './tests/global-teardown.ts',
};
// tests/global-setup.ts
export 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.test.yml
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 -d
DATABASE_URL=postgres://postgres:test@localhost:5433/myapp_test npm test
docker compose -f docker-compose.test.yml down
prisma/test-setup.ts
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 + EmailService
describe('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);
});
});
  1. Напиши integration тест для REST API с тестовой БД (PostgreSQL в Docker)
  2. Протестируй сценарий: регистрация → логин → получение профиля
  3. Настрой Docker Compose для автоматического поднятия тест-окружения
  • Integration тесты проверяют реальное взаимодействие модулей
  • supertest — стандарт для тестирования HTTP API
  • Тестовая БД в Docker (tmpfs) — быстро и изолированно
  • beforeEach → очистка данных между тестами