Express / 37 минут
Интеграционные тесты API
Проверяйте HTTP-контракт Supertest, изоляцию состояния и отрицательные сценарии.
- Проверяйте HTTP-контракт Supertest, изоляцию состояния и отрицательные сценарии.
- Применить тему в задаче «Интеграционные тесты API» и объяснить граничные случаи.
Около 7 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.
Почему нужен запрос через HTTP-границу
Функция сервиса может правильно вернуть объект, но маршрут ошибётся в пути, middleware не разберёт body или ответ случайно раскроет лишнее поле. Интеграционный HTTP-тест проверяет совместную работу этих частей. Он не заменяет все unit-тесты, но ловит риски, которых прямой вызов чистой функции не видит.
Supertest позволяет направить запрос Express-приложению и проверить статус, заголовки и тело. Не нужно поднимать постоянный порт, который разделяется всеми тестами. Если приложение создаётся фабрикой с зависимостями, каждый сценарий может получить чистое хранилище и предсказуемые ответы без реального внешнего сервиса.
Базовая структура теста
Тест сначала создаёт app, затем выполняет запрос и проверяет наблюдаемый контракт. Удобно независимо утверждать статус и полезные поля JSON. Проверка только 200 недостаточна: handler может вернуть совершенно не те данные. У публичного пользователя отдельно полезно подтвердить отсутствие passwordHash, а не только наличие id.
const request = require('supertest');
const response = await request(app)
.get('/health')
.expect('content-type', /json/)
.expect(200);
assert.deepEqual(response.body, { ok: true });Этому фрагменту нужны импорт assert и подготовленное приложение из вашего тестового окружения. Сама логика проверки не зависит от имени тестового runner: он управляет запуском и сообщением о результате. Главное — дождаться Promise запроса, чтобы тест не завершился раньше фактической проверки.
Положительные и отрицательные сценарии
Для POST с непустым title проверьте обычную строку, пробелы, отсутствие поля и неверный тип. Для ресурса по id — существование, отсутствие и запрет доступа чужому пользователю. Для фильтра — корректную страницу, границы и недопустимую строку. Каждый пример должен проверять отдельное правило, а не повторять один успешный ответ с другим названием.
await request(app).post('/notes').send({ title: 'JS' }).expect(201);
await request(app).post('/notes').send({ title: ' ' }).expect(400);
await request(app).post('/notes').send({ title: 42 }).expect(400);При отказе полезно также проверить отсутствие эффекта: заметка не создана, чужая запись не изменилась, XP не начислен второй раз. Статус может быть правильным при уже произошедшем нежелательном изменении. Поведение всей операции важнее отдельной строки ответа.
Изоляция данных и зависимостей
Не используйте один изменяемый массив всех тестов без явного сброса. Порядок запуска и параллельность способны изменить результаты. Для unit-теста сервиса подойдёт новый fake-репозиторий, для интеграции с БД — отдельная тестовая база и управляемая очистка. Никогда не направляйте тест удаления на пользовательскую production-базу ради удобства.
function createMemoryRepository() {
const notes = new Map();
return {
find: id => notes.get(id),
save: note => notes.set(note.id, note),
};
}Такая подмена не доказывает реальные свойства SQL-транзакций или уникальных ограничений. Для них нужна отдельная проверка настоящей инфраструктуры. Напротив, тест маршрута не обязан зависеть от живого почтового сервера, если задача — проверить выбор статуса и публичного DTO после успешного сценария.
Сессии, асинхронность и cleanup
Защищённые запросы проверяют с настоящим тестовым механизмом входа или контролируемой подменой authentication-границы. Успешный тест не должен случайно обходить саму проверяемую авторизацию. Cookies и ожидаемые Origin/CSRF-заголовки передают так же осознанно, как их использует настоящий клиент.
Серверы и ресурсы нужно закрывать в finally или lifecycle тестового окружения. Фиксированная задержка не является надёжным cleanup. Если async-операция отклоняется, тест должен дождаться и проверить нужную ветвь. Оставленные таймеры и соединения часто объясняют, почему «все assertions прошли», но процесс не завершился.
Практика набора параметров
Задание создаёт GET /double/:n и выполняет несколько запросов. Разрешены только цифры, поэтому '-1' и '12px' должны дать 400. Пустой список запросов возвращает пустой массив статусов. Реальная проверка параметра происходит внутри HTTP-маршрута, а не в обход приложения на входе solve.
После решения добавьте проверку содержимого успешного body и JSON-формы отказа. Для итогового API заметок составьте матрицу CRUD-сценариев и прав. Хороший набор тестов должен объяснять контракт будущему читателю и оставаться зелёным после безопасного переноса логики между helper-функциями без изменения поведения.