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