Практическая ситуация: сервис изменяет локальное состояние, затем получает отказ внешней зависимости и возвращает ошибку клиенту. Какой интеграционный инвариант должен проверить тест?
Тест должен проверить, что после отказа внешней зависимости локальное состояние не осталось в промежуточном, подтверждённом виде. Оно должно либо полностью откатиться, либо перейти в явно предусмотренное состояние компенсации, например «ожидает повторной обработки».
Проверки только HTTP-статуса недостаточно: ответ может быть ошибочным, а локальная запись уже изменённой.
Такой подход появился из-за того, что операция между несколькими системами редко выполняется одной общей транзакцией. Локальная база данных и внешняя зависимость обычно имеют отдельные границы фиксации, поэтому сбой между шагами может оставить их состояния несогласованными.
Интеграционные проверки стали использовать для выявления таких промежуточных состояний, которые не видны в изолированных модульных тестах с полностью успешными моками.
Предположим, сервис резервирует товар в своей базе, затем обращается к платёжной системе. Платёжная система отвечает отказом, но резерв уже зафиксирован. Если тест проверяет только код ответа, он пропустит занятый навсегда товар или заказ, который выглядит как успешно обработанный.
Нужно определить ожидаемый инвариант: после неуспешной операции нельзя оставлять состояние, которое означает подтверждённый результат. Допустимы только откат либо явно описанный переход в промежуточное состояние с последующей обработкой.
Интеграционный тест подготавливает исходное состояние, вызывает основной API, заставляет внешнюю зависимость вернуть контролируемый отказ и затем повторно читает состояние через публичный интерфейс или проверяемый интеграционный слой.
Основная проверка состоит из двух частей:
Если операция действительно атомарна внутри локальной базы, изменение и запись результата должны находиться в одной локальной транзакции. Если внешний вызов нельзя включить в эту транзакцию, обычно применяют сагу, компенсационное действие или надёжную очередь повторной обработки. Тест должен проверять именно выбранный бизнес-сценарием результат, а не требовать безусловного отката.
Мок внешней зависимости здесь должен моделировать конкретный отказ: например, бизнес-отказ или временную недоступность. При этом важно проверить, что мок был вызван ожидаемое число раз; иначе тест может пройти, даже если сервис вообще не выполнил внешний шаг.
Компромисс состоит в выборе границы проверки. Чтение базы напрямую быстрее, но может скрыть ошибки публикации состояния через API. Проверка только через API ближе к поведению пользователя, однако сложнее отличить ошибку бизнес-логики от проблемы чтения. Для критичных операций полезно сочетать публичную проверку результата с отдельной проверкой технических признаков компенсации или повторной обработки.
Сервис оформлял возврат: сначала менял статус заказа на «возвращён», затем запрашивал возврат денег у платёжного провайдера. Интеграционный тест подставлял отказ провайдера и проверял только ответ 502. Тест проходил, хотя повторный запрос заказа показывал статус «возвращён», а деньги клиенту не были возвращены.
Рассматривались два варианта. Полный откат статуса был простым для клиента, но плохо подходил для временной недоступности провайдера: повтор операции терялся. Немедленная компенсация могла исправить статус, но добавляла ещё один внешний вызов и усложняла обработку повторных сбоев.
Выбрали состояние «возврат ожидает обработки» с записью задания на повтор. Тест после отказа проверял именно этот статус, наличие задания и отсутствие статуса «возвращён». Дополнительный прогон после успешного повтора подтверждал окончательное изменение состояния; так тест проверял и отказоустойчивость, и завершение сценария.
Нет. Отсутствие записи подходит только для операций, где бизнес-правило действительно требует полного отката. Для долгих или повторяемых процессов корректным результатом может быть сохранённая запись со статусом ожидания и данными для безопасного продолжения.
Откат отменяет локальные изменения в рамках ещё не завершённой локальной транзакции. Компенсация выполняет отдельное действие после фиксации исходного изменения, поэтому сама может завершиться ошибкой и должна быть идемпотентной или защищённой от повторного применения. Тест должен проверять разные ожидаемые состояния для этих механизмов.
Мок должен быть подключён к реальному интеграционному пути и возвращать отказ в момент, когда сервис действительно обращается к зависимости. Нужно подтвердить факт и кратность вызова, затем проверить состояние после завершения обработки. Иначе тест может лишь доказать, что сервис умеет обрабатывать искусственно заданный ответ, но не выявить ошибку в маршрутизации, транзакционной границе или повторной обработке.