Письмо подтверждения — это та часть сценария регистрации, которую браузерный тест не может пройти в одиночку. Кто-то должен владеть адресом, получить письмо и вернуть код тесту. API временного почтового ящика делает именно это: набор тестов просит сервис создать ящик по HTTP, читает то, что пришло, и выбрасывает ящик, когда сценарий завершён. Здесь нет браузера, нет общего человеческого ящика и нет ручного копирования.
Эта статья — про сторону API в такой схеме. Она не про то, как направить приложение на локальный приёмник: это тема материала о перехвате писем в CI, и две задачи решают разные проблемы. Локальный приёмник доказывает, что отправляет ваше приложение. API ящика доказывает, что действительно приходит — по реальному пути доставки, на адрес, которым владеет тест.
Зачем использовать API ящика в тестах?
Потому что альтернатива — либо настоящий человеческий ящик, либо ничего.
Общий ящик — плохое приспособление. Несколько прогонов читают один и тот же ящик, письма прошлого прогона всё ещё лежат там, а адрес копит трафик, которого ни один тест не просил. Тогда каждая проверка вынуждена угадывать, какое письмо относится к текущему сценарию, а в угадывании рождается нестабильность.
Собирать веб-интерфейс провайдера браузером — второй плохой вариант. Он привязывает тест к разметке, которая меняется без предупреждения, к состоянию сессии и к входу, за которым набор тестов должен присматривать. Как только провайдер меняет вид кнопки, зелёный набор краснеет по причине, никак не связанной с продуктом.
API ящика убирает обе проблемы. Адрес создаётся под сценарий, он пуст по построению, и читается он через стабильный интерфейс, который тест может вызывать напрямую. Набору тестов больше не важно, как выглядит провайдер, — важно лишь, что контракт держится: создать, получить, прочитать, удалить.
Есть и довод о приватности. Адрес, созданный через API, никого не представляет. Это не ящик человека, это не место, куда могло бы упасть настоящее письмо, и он выбрасывается вместе с прогоном.
Какие эндпоинты действительно нужны тесту?
API ящика может открывать десятки маршрутов, но тестовому клиенту нужны четыре операции, и полезно назвать их так, как их будет использовать набор тестов.
Создать возвращает адрес и дескриптор. Адрес — это то, куда тестируемому приложению велено отправлять. Дескриптор, часто токен или идентификатор, — это то, чем тест спрашивает об этом ящике в каждом последующем вызове. Набор тестов должен обращаться с парой как с одним объектом и никогда не восстанавливать дескриптор из адреса, потому что провайдер вправе сделать их никак не связанными.
Список возвращает сводки, а не тела: по записи на письмо, с идентификатором, отправителем, темой и временем прихода. Это вызов, который должен использовать цикл опроса, потому что он дешёвый и его достаточно, чтобы ответить на единственный важный вначале вопрос: пришло ли уже что-нибудь.
Прочитать возвращает одно письмо целиком, включая текстовую и HTML-части. Именно там живёт код, и это вызов, который стоит делать только после того, как список сообщил о совпадении.
Очистить убирает письма из ящика или удаляет ящик целиком. Тесту оно нужно по двум причинам: сбросить состояние между попытками, не создавая новый адрес, и прибрать, когда сценарий завершён.
Некоторые сервисы добавляют эндпоинт ожидания или длинного опроса, который держит соединение до прихода письма или истечения срока. Это удобно, но клиент всё равно должен уметь откатиться к списку, потому что именно вызов ожидания чаще всего упирается в ограничение частоты.
Опрос кода без нестабильности
Самая частая ошибка в таком тесте — фиксированная пауза. Постоянное число секунд — это догадка: слишком короткая при медленной доставке, расточительно длинная при быстрой и неверная в обе стороны на загруженном исполнителе CI. Замените её циклом, который вызывает список, ищет совпадение и возвращается сразу, как только оно найдено, с потолком, который роняет тест, а не подвешивает задачу.
Сопоставление — вторая половина задачи. Нужное письмо — то, что адресовано адресу, созданному сценарием, и, если ящик может хранить не один вид почты, то, в теме которого есть устойчивый фрагмент. Предпочитайте самое свежее совпадение, чтобы повторная доставка из-за повтора не спутала чтение. Никогда не берите первое письмо без условий: на переиспользованном адресе именно так и подтверждается старый код.
Извлечение должно быть привязанным. В теле могут быть номер обращения, отметка времени и цена, и разбор, хватающий первый ряд цифр, иногда хватает одно из них вместо кода. Ищите формулировку, которая вводит код, читайте код из её окружения и падайте с приложенным телом, когда ничего не совпало.
И наконец, уважайте ограничение на повторную отправку. Сценарий с кодом обычно допускает лишь несколько отправок за короткое окно, и это ограничение — часть проверяемого поведения. Тест, который нажимает кнопку снова ради свежего кода, рано или поздно получит отказ и упадёт по неверной причине. Повторяйте, читая ящик заново, а не вызывая новое письмо.
Как встроить это в сквозной набор тестов или в CI
Самая чистая форма — приспособление. До старта сценария оно создаёт ящик и возвращает его адрес. Тест ведёт приложение, используя этот адрес. После того как приложение подтвердило отправку, проверка читает ящик и извлекает код. Когда сценарий заканчивается, приспособление удаляет ящик.
Держите клиент маленьким и подставляемым. Один модуль оборачивает четыре вызова; тест зависит от этого модуля, а не от сырого HTTP, разбросанного по набору. Это позволяет подменить его заглушкой в модульных тестах и направить тот же набор на другого провайдера, не переписывая проверки.
В CI учётные данные живут в хранилище секретов задачи, не в репозитории и не в строке журнала. Дайте каждой задаче или каждому параллельному исполнителю свой ящик и поставьте перед сгенерированными адресами префикс, отмечающий прогон, чтобы заблудившееся письмо можно было отнести по виду. Поставьте таймаут клиента ниже таймаута самой задачи, чтобы застрявший опрос падал с ясным сообщением, а не резкой отменой задачи.
Повторяйте чтение, а не весь сценарий. Если код ещё не пришёл, подождите и прочитайте снова; повторный запуск регистрации породит второе письмо и вместе с ним второго кандидата для проверки. И держите API ящика подальше от продакшн-сценариев: это тестовая инфраструктура, и набор тестов никогда не должен иметь возможности отправить письмо настоящему клиенту из неё.
Сам сценарий вокруг кода, а не механику его чтения, разбирает материал про тестирование потока подтверждения почты, а именно этап разбора — тема материала про код подтверждения в сквозных тестах.
Изоляция и уборка
Один адрес на сценарий — правило, которое предотвращает большинство сбоев между тестами. Оно убирает нужду рассуждать о том, какое письмо кому принадлежит, и заставляет вопрос свежести исчезнуть, потому что ящик получал трафик только одного сценария.
Уборка должна быть явной и безусловной. Удаляйте ящик в завершении, которое выполняется и при успехе, и при падении, а не только на счастливом пути. Полагаться лишь на срок жизни у провайдера — ошибка: письмо может задержаться достаточно, чтобы его прочитал следующий прогон на той же машине, и этот срок — удобство, а не гарантия.
Если уборка не удалась, запишите это в журнал и дайте набору завершиться. Ошибка уборки стоит того, чтобы о ней знать, но она не то же самое, что дефект продукта, и валить из-за неё прогон — значит учить команду игнорировать сбои завершения. Относитесь ко всему, что получил адрес, как к тестовым данным: оно существует для одной проверки, его не следует экспортировать или передавать, и его никогда нельзя считать чьей-либо контактной точкой.
Ограничения и оговорки
API ящика — всё ещё сторонняя зависимость, и её пределы становятся вашими. Ограничения по частоте за минуту могут отказать в серии созданий из большого параллельного прогона. Квоты ограничивают, сколько адресов существует одновременно. Письма могут задерживаться, а задержавшееся письмо выглядит точно как пропавшее, пока не придёт.
Одноразовые домены к тому же широко блокируются. Домен провайдера может быть отвергнут той самой формой регистрации, которую вы тестируете, и это превращает законный тест в сбивающее с толку падение. Когда так происходит, ответ не в том, чтобы делать исключение для провайдера, а в том, чтобы понять, отвергает ли тестируемый продукт одноразовые адреса намеренно, и целенаправленно проверять именно это поведение.
Честный итог таков: API ящика — верный инструмент, чтобы утверждать, что приходит. Чтобы утверждать, что выдаёт ваше приложение, локальный приёмник быстрее и не имеет квоты. Большинство зрелых наборов используют и то и другое: локальный приёмник для основной массы проверок, а API ящика — только там, где предметом теста является реальный путь доставки.
Что делать дальше
Найдите в наборе тест, который читает код, опрашивая общий ящик, и замените его приспособлением, создающим свежий адрес через API ящика. Записывайте адрес вместе с прогоном, удаляйте его в завершении и смотрите, сколько нестабильности, которую вы терпели, просто исчезнет. Когда ящик нужен вручную, страница временной почты создаёт его в один момент, а адреса, которые она выдаёт, — это строительные леса для прогона, никогда не настоящая личность.