Когда разработчик говорит «я написал API», это ещё не значит, что он работает. Точнее, работает — но как именно? Что вернёт эндпоинт при некорректных данных? Выдержит ли нагрузку? Не сломается ли авторизация?
Тестирование API — это не просто «запустил, получил 200, всё ок». Это системная проверка того, что сервис ведёт себя предсказуемо в любых условиях. Разберём, чем тестировать, как структурировать проверки и на что смотреть в первую очередь.
Зачем вообще тестировать API отдельно
UI-тесты не заменяют API-тесты. Если вы кликаете по кнопке в браузере и проверяете, что данные появились на странице — вы тестируете стек целиком. Это полезно, но медленно и нестабильно.
API-тесты работают на уровне HTTP-запросов: напрямую, без браузера, без фронтенда. Они быстрее, стабильнее и точнее говорят, где именно сломалось.
Кроме того, многие API живут отдельно от UI — это внутренние сервисы, микросервисы, интеграции. Там вообще нет фронтенда, который можно потыкать.
curl — минимальный инструмент, который всегда под рукой
Прежде чем открывать Postman, стоит знать curl. Это консольная утилита, которая есть в любой Unix-системе и доступна на Windows. Она отправляет HTTP-запросы и показывает ответ.
Простой GET-запрос:
curl https://api.example.com/users
POST с JSON-телом и заголовком авторизации:
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJhbGc..." \
-d '{"name": "Ivan", "email": "ivan@example.com"}'
Флаг -v покажет полные заголовки запроса и ответа — удобно для отладки. -I вернёт только заголовки без тела.
curl — это инструмент для быстрой проверки «а вообще что-то отвечает?». Для систематического тестирования нужно что-то удобнее.
Postman — стандарт индустрии
Postman — графический инструмент для работы с API. Бесплатная версия покрывает большинство задач.
Что умеет Postman:
- Сохранять запросы в коллекции и организовывать их по папкам
- Использовать переменные окружения (dev/staging/prod с разными базовыми URL)
- Писать тесты на JavaScript прямо внутри запроса
- Запускать коллекцию как набор тестов через Collection Runner
- Генерировать документацию по коллекции
Пример теста в Postman — вкладка Tests:
pm.test("Статус 200", function () {
pm.response.to.have.status(200);
});
pm.test("Ответ — массив", function () {
const body = pm.response.json();
pm.expect(body).to.be.an('array');
});
pm.test("Первый пользователь имеет email", function () {
const body = pm.response.json();
pm.expect(body[0]).to.have.property('email');
});
Эти тесты запускаются после каждого запроса и показывают, прошли они или нет. Collection Runner запускает всю коллекцию последовательно — это уже похоже на настоящий тест-сьют.
Postman также поддерживает pre-request скрипты — код, который выполняется перед запросом. Удобно для получения токена перед авторизованными запросами.
Insomnia — альтернатива для тех, кому Postman тяжеловат
Insomnia — более лёгкий и быстрый инструмент. Интерфейс проще, меньше функций, но для большинства задач хватает. Хорошо работает с GraphQL и gRPC, где у Postman исторически были проблемы.
Если вы работаете с REST и вам нужна простая коллекция запросов — выбор между Postman и Insomnia это вопрос вкуса.
HTTPie — curl для людей
HTTPie — консольный инструмент, синтаксис которого значительно удобнее curl:
http POST api.example.com/users name=Ivan email=ivan@example.com Authorization:"Bearer token123"
Без кавычек вокруг JSON, без -H для каждого заголовка, с подсветкой синтаксиса в выводе. Если вы часто работаете в терминале, HTTPie ускоряет рутину.
Автоматизация: когда ручных запросов уже недостаточно
Ручное тестирование хорошо для отладки, но оно не масштабируется. Нельзя каждый деплой проверять руками — это медленно и ненадёжно.
Для автоматизации API-тестов чаще всего используют:
Jest + Supertest (Node.js). Supertest — библиотека для тестирования HTTP-сервисов. Работает поверх Jest или Mocha.
const request = require('supertest');
const app = require('../app');
describe('GET /users', () => {
it('возвращает список пользователей', async () => {
const res = await request(app).get('/users');
expect(res.statusCode).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
});
it('возвращает 401 без токена', async () => {
const res = await request(app).get('/users/me');
expect(res.statusCode).toBe(401);
});
});
Pytest + httpx (Python). httpx — асинхронный HTTP-клиент с удобным API для тестирования.
import httpx
def test_get_users():
response = httpx.get('https://api.example.com/users')
assert response.status_code == 200
assert isinstance(response.json(), list)
Newman — CLI-runner для Postman-коллекций. Позволяет запускать коллекцию в CI/CD:
newman run collection.json -e environment.json --reporters cli,junit
JUnit-репорт понимают большинство CI-систем — GitLab CI, GitHub Actions, Jenkins.
Что именно проверять
Новички часто ограничиваются проверкой статус-кода. Это необходимо, но недостаточно. Вот что стоит проверять:
Статус-коды. Не только 200, но и граничные случаи: 400 при невалидных данных, 401 без токена, 403 при недостатке прав, 404 для несуществующего ресурса, 422 при нарушении бизнес-логики.
Структура ответа. Есть ли нужные поля? Правильные ли типы данных? Не пришёл ли null вместо массива?
Содержимое данных. Создали пользователя — вернулся ли он с правильным email? Обновили поле — отразилось ли изменение?
Заголовки. Content-Type должен быть application/json для JSON-API. Если есть кеширование — Cache-Control. Для CORS — нужные заголовки при preflight-запросе.
Время ответа. Медленный API — плохой API. Если эндпоинт отвечает дольше 500 мс при нормальной нагрузке, это повод разобраться.
Пагинация. Если API возвращает списки — проверяйте пагинацию: корректные meta.total, правильная навигация по страницам, работа с limit/offset или cursor.
Нагрузочное тестирование: выдержит ли API реальную нагрузку
Функциональные тесты проверяют корректность. Нагрузочные — производительность под стрессом.
k6 — современный инструмент для нагрузочного тестирования, скрипты на JavaScript:
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 100, // 100 виртуальных пользователей
duration: '30s', // 30 секунд теста
};
export default function () {
const res = http.get('https://api.example.com/users');
check(res, {
'статус 200': (r) => r.status === 200,
'быстрее 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}
k6 покажет p95, p99 времени ответа, количество ошибок, RPS. Запускать его стоит не в продакшн, а на стейджинге.
Apache Benchmark (ab) — проще, встроен в большинство систем:
ab -n 1000 -c 50 https://api.example.com/users
1000 запросов, 50 одновременных соединений. Быстрый способ понять, как сервер справляется с нагрузкой.
Тестирование авторизации и безопасности
Это отдельная область, которую часто игнорируют. Минимальный чек-лист:
- Запрос без токена возвращает 401
- Токен другого пользователя не даёт доступ к чужим данным (проверяйте IDOR — Insecure Direct Object Reference)
- Истёкший токен отклоняется
- SQL-инъекции в параметрах: передайте
' OR 1=1 -- в строковое поле и убедитесь, что API не ломается
- Слишком длинные строки не роняют сервер
- Вложенные объекты большой глубины не вызывают переполнение стека
Для автоматизации безопасного тестирования используют OWASP ZAP или Burp Suite, но это уже отдельная тема.
Contract testing: когда микросервисов много
Если у вас монолит с одним API — достаточно функциональных тестов. Но если несколько команд разрабатывают микросервисы, которые общаются друг с другом, возникает проблема: команда А изменила формат ответа, команда Б об этом не знала — всё сломалось.
Contract testing решает это: каждый сервис фиксирует «контракт» — что он ожидает получить и что обязуется вернуть. При изменении контракта тесты падают до того, как изменение попадёт в продакшн.
Pact — самый популярный инструмент для contract testing. Поддерживает JavaScript, Python, Go, Java.
Для большинства небольших проектов contract testing избыточен. Он нужен, когда у вас 5+ микросервисов и несколько команд.
Как встроить тесты в CI/CD
Тесты без автоматического запуска — это тесты, которые никто не запускает. Правильный подход:
- Функциональные тесты запускаются при каждом пуше в ветку
- Нагрузочные тесты — по расписанию или перед релизом
- Упавшие тесты блокируют мёрж в main
Пример для GitHub Actions:
name: API Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm install
- run: npm test
Для Newman:
- name: Run API tests
run: newman run collection.json -e env.json
Мониторинг API в продакшне
Тесты проверяют API до деплоя. Мониторинг — после. Это разные задачи.
Синтетический мониторинг — запускает реальные HTTP-запросы к продакшн-API по расписанию и алертит, если что-то пошло не так. Инструменты: Checkly, UptimeRobot, Grafana с HTTP-зондами.
АПИ может работать, но медленно. Настройте алерты не только на ошибки, но и на деградацию времени ответа.
Как организовать тесты в проекте
Несколько правил, которые упрощают жизнь:
Один тест — одна проверка. Не мешайте в один тест проверку статуса, структуры и бизнес-логики. Если что-то падает, сразу понятно, что именно.
Независимые тесты. Тест не должен зависеть от результата предыдущего. Если нужен созданный пользователь — создайте его в setup, удалите в teardown.
Реалистичные данные. Используйте фикстуры, похожие на боевые данные. Тест с name: "test" и email: "a@b.c" менее надёжен, чем с полноценными данными.
Покрывайте негативные сценарии. 80% багов в API — это граничные случаи и некорректный ввод. Тестируйте не только «счастливый путь».
Если вы заказываете разработку API — убедитесь, что подрядчик поставляет тесты вместе с кодом. В REEXY (r3xy.ru) интеграция сервисов и разработка API включают проверку ключевых сценариев — так проект приходит с доказательством, что он работает, а не просто выглядит работающим.
Начать можно просто: curl или Postman, несколько ручных запросов, понимание структуры ответов. Потом — тесты на Jest или Pytest, запуск в CI. Нагрузка и безопасность — следующий уровень, когда базовое покрытие есть.
Главное: тест, написанный сегодня, завтра поймает регрессию, на которую иначе потратите час отладки в продакшне.