Антон Ларичев

Введение
Тестирование API — обязательная практика для любого проекта, который работает с серверной логикой. Без тестов вы узнаете о сломанном эндпоинте только от пользователей, а не на этапе разработки. Современный стек из Vitest и Playwright позволяет покрыть весь спектр: от быстрых unit-тестов отдельных функций до полноценных e2e-сценариев, проверяющих API от запроса до ответа.
В этой статье разберём, как выстроить стратегию тестирования API на практике. Начнём с unit-тестов бизнес-логики в Vitest, перейдём к интеграционным тестам с моками через MSW и закончим e2e-тестами реального API с помощью Playwright. Каждый уровень — с примерами кода на TypeScript.
Unit-тесты для API-логики в Vitest
Unit-тесты проверяют отдельные функции в изоляции. Для API это валидаторы, трансформеры данных, middleware и бизнес-логика обработчиков.
Настройка Vitest для проекта
Установите Vitest и запустите первый тест:
npm install -D vitest
Добавьте скрипт в package.json:
{
"scripts": {
"test": "vitest",
"test:run": "vitest run"
}
}
Как писать unit-тесты для валидации запросов
Допустим, у вас есть функция валидации тела запроса:
// src/validators/user.ts
export function validateCreateUser(body: unknown): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (!body || typeof body !== 'object') {
return { valid: false, errors: ['Тело запроса должно быть объектом'] };
}
const data = body as Record<string, unknown>;
if (!data.email || typeof data.email !== 'string') {
errors.push('Email обязателен');
}
if (!data.name || typeof data.name !== 'string' || (data.name as string).length < 2) {
errors.push('Имя должно содержать минимум 2 символа');
}
return { valid: errors.length === 0, errors };
}
Тест для этой функции:
// src/validators/user.test.ts
import { describe, it, expect } from 'vitest';
import { validateCreateUser } from './user';
describe('validateCreateUser', () => {
it('принимает корректные данные', () => {
const result = validateCreateUser({ email: 'test@mail.com', name: 'Иван' });
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it('отклоняет пустое тело запроса', () => {
const result = validateCreateUser(null);
expect(result.valid).toBe(false);
expect(result.errors).toContain('Тело запроса должно быть объектом');
});
it('требует email и имя', () => {
const result = validateCreateUser({ email: '', name: 'A' });
expect(result.valid).toBe(false);
expect(result.errors.length).toBeGreaterThanOrEqual(1);
});
});
Unit-тесты в Vitest запускаются за миллисекунды. Это ваша первая линия защиты от регрессий.
Интеграционные тесты API с моками HTTP-запросов
Интеграционные тесты проверяют взаимодействие нескольких модулей. Для тестирования API без реального сервера используют Mock Service Worker (MSW), который перехватывает HTTP-запросы на сетевом уровне.
Настройка MSW с Vitest
npm install -D msw
Создайте обработчики моков:
// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/api/users/:id', ({ params }) => {
const { id } = params;
return HttpResponse.json({
id: Number(id),
name: 'Тестовый пользователь',
email: 'test@example.com',
});
}),
http.post('/api/users', async ({ request }) => {
const body = await request.json();
return HttpResponse.json({ id: 1, ...body }, { status: 201 });
}),
];
Подключите MSW в тестах:
// src/services/api.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { setupServer } from 'msw/node';
import { handlers } from '../mocks/handlers';
const server = setupServer(...handlers);
beforeAll(() => server.listen());
afterAll(() => server.close());
describe('API клиент', () => {
it('получает пользователя по ID', async () => {
const response = await fetch('/api/users/1');
const data = await response.json();
expect(response.status).toBe(200);
expect(data.name).toBe('Тестовый пользователь');
});
it('создаёт нового пользователя', async () => {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Новый', email: 'new@test.com' }),
});
const data = await response.json();
expect(response.status).toBe(201);
expect(data.name).toBe('Новый');
});
});
MSW работает на уровне сетевого стека, поэтому ваш код использует настоящий fetch — моки прозрачны для тестируемого кода.
E2E-тестирование API с Playwright
E2E-тесты проверяют реальный API на работающем сервере. Playwright предоставляет встроенный APIRequestContext для отправки HTTP-запросов без браузера.
Настройка Playwright для тестирования API
npm install -D @playwright/test
Создайте конфигурацию:
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
baseURL: 'http://localhost:3000',
},
projects: [
{
name: 'api-tests',
testDir: './tests/api',
},
],
});
Как тестировать REST API эндпоинты в Playwright
// tests/api/users.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Users API', () => {
let createdUserId: number;
test('POST /api/users — создание пользователя', async ({ request }) => {
const response = await request.post('/api/users', {
data: {
name: 'Playwright User',
email: 'playwright@test.com',
},
});
expect(response.status()).toBe(201);
const body = await response.json();
expect(body.id).toBeDefined();
expect(body.name).toBe('Playwright User');
createdUserId = body.id;
});
test('GET /api/users/:id — получение пользователя', async ({ request }) => {
const response = await request.get(`/api/users/${createdUserId}`);
expect(response.status()).toBe(200);
const body = await response.json();
expect(body.email).toBe('playwright@test.com');
});
test('DELETE /api/users/:id — удаление пользователя', async ({ request }) => {
const response = await request.delete(`/api/users/${createdUserId}`);
expect(response.status()).toBe(200);
// Проверяем, что пользователь действительно удалён
const check = await request.get(`/api/users/${createdUserId}`);
expect(check.status()).toBe(404);
});
});
Playwright выполняет реальные HTTP-запросы к серверу, проверяя полный цикл обработки: роутинг, middleware, базу данных и ответ.
Тестирование авторизации и заголовков
test('защищённый эндпоинт без токена возвращает 401', async ({ request }) => {
const response = await request.get('/api/admin/stats');
expect(response.status()).toBe(401);
});
test('защищённый эндпоинт с токеном возвращает данные', async ({ request }) => {
const response = await request.get('/api/admin/stats', {
headers: {
Authorization: 'Bearer test-jwt-token',
},
});
expect(response.status()).toBe(200);
});
Частые ошибки при тестировании API
Тестирование только happy path. Проверяйте не только успешные сценарии, но и ошибки: невалидные данные, отсутствующие ресурсы, таймауты. E2e-тесты без негативных сценариев дают ложную уверенность.
Зависимость между тестами. Каждый тест должен работать независимо. Если один тест создаёт данные для другого, используйте beforeEach для подготовки состояния.
Отсутствие тестов на контракт. Проверяйте структуру ответа, а не только статус-код. Если API возвращает 200, но с неправильным форматом — это баг.
Игнорирование таймаутов. В e2e-тестах всегда задавайте разумные таймауты. Playwright позволяет настроить их глобально и для отдельных запросов.
Стратегия: какие тесты писать для API
Оптимальная пирамида тестирования API:
- Unit-тесты (70%) — Vitest: валидаторы, трансформеры, утилиты, бизнес-логика. Быстрые, дешёвые, запускаются на каждый коммит.
- Интеграционные (20%) — Vitest + MSW: взаимодействие модулей, API-клиенты, обработка ответов. Средняя скорость, проверяют контракты.
- E2E (10%) — Playwright: критические пользовательские сценарии, авторизация, CRUD-операции. Медленные, но проверяют реальную систему.
Такое распределение обеспечивает быструю обратную связь от unit-тестов и надёжность от e2e, не замедляя CI/CD пайплайн.
Заключение
Тестирование API не требует одного инструмента на все случаи. Vitest отлично справляется с unit-тестами и интеграционными проверками благодаря скорости и поддержке ESM. Playwright закрывает e2e-уровень, отправляя реальные запросы к серверу. Вместе они покрывают весь спектр — от проверки одной функции до полного сценария с авторизацией, CRUD и обработкой ошибок. Начните с unit-тестов критичной логики, добавьте интеграционные тесты с MSW и завершите e2e-проверками ключевых эндпоинтов.






Комментарии
0