Field note · 使用教程

2026 Laravel 13 邮件测试:Mail::fake 能证明邮件送达吗?

这篇文章面向正在测试 Laravel 注册验证、密码重置或通知邮件的开发者、QA 工程师和独立开发者。我们按应用调用、队列状态、邮件内容和外部收件拆分证据边界,并给出按验收目标选择测试组合的条件分支与操作步骤。

Laravel 13 的 MailFake API 将 assertSent() 和 assertQueued() 分成两个断言入口;这两个入口分别核对发送调用与排队行为,并不代表外部收件箱已经收到邮件。Laravel 13 MailFake API 本周建议:继续用 Mail::fake() 覆盖业务调用,但只要验收目标写着“收件人实际收到”,就补一轮受控真实收件。单独的 Mail::fake() 不能证明邮件经过真实传输后抵达了收件箱。

适合正在测试注册验证、密码重置或通知邮件的开发者。
也适合需要为邮件回归设置明确通过证据的 QA 工程师。
维护预发环境、希望检查邮件但不误发给真实用户的独立开发者,也可以照此拆分测试层。

Laravel 13 邮件测试:三类证据分别能证明什么?

把“邮件测试通过”拆成不同结论,才能知道断言覆盖了哪一段流程。我们在测试记录里至少区分这三类证据:

  • 应用调用证据:业务逻辑要求发送了指定的 Mailable,目标收件人或调用次数符合预期。
  • 内容结构证据:发件人、收件人、主题、HTML 或纯文本正文等内容符合预期。
  • 外部收件证据:测试地址的收件箱中实际出现邮件,并且邮件可读、预期链接正确。

前两类可以在框架测试中检查。第三类必须让邮件离开测试替身,经实际邮件链路发送,并在受控收件地址核验。即使某次真实收件通过,也只证明那次测试的配置、发送路径和收件条件下观察到了邮件;不能据此保证所有收件服务、用户地址或未来发送都成功。

这是应用测试与收件验收之间最重要的边界:断言可以证明代码路径和生成内容,不能替代外部收件结果。

Mail::fake() 适合验证调用,不适合证明投递

在注册流程中,可以先触发自家产品的注册动作,再断言验证邮件是否针对预期用户发出。Mail::fake() 会拦截邮件发送,避免测试期间向真实收件人发信;之后可以通过 assertSent() 检查指定 Mailable 是否被发送,并在回调中核对收件人等条件。Laravel 邮件测试文档介绍了这种断言方式及 Mailable 检查方法。Laravel 13 邮件测试文档

use App\Mail\VerifyAccount;
use Illuminate\Support\Facades\Mail;

Mail::fake();

// 执行自家应用的注册流程
$this->post('/register', $validRegistrationData);

Mail::assertSent(VerifyAccount::class, function (VerifyAccount $mail) use ($user) {
    return $mail->hasTo($user->email);
});

这段测试可以支持“注册流程触发了指定验证邮件,并且收件人匹配”这一结论。它不能支持“邮件服务已接受邮件”“邮件没有进入垃圾箱”或“收件人能打开邮件”这些结论,因为 fake 的目的正是阻止真实邮件发送。

发送次数也应按业务预期写清。Laravel API 提供 assertSentTimes(),可以用次数断言检查重复提交、重试或错误分支是否造成多发;但通过次数断言仍然只是应用层证据,不是投递回执。

assertSent 和 assertQueued:发送调用与入队不是一回事

邮件同步发送时,使用 assertSent();业务预期是将 Mailable 排队时,使用 assertQueued()。 不要只看 HTTP 请求返回成功,就推断后台任务已经执行,更不要把“进入队列”写成“邮件已经送达”。

Laravel 的 MailFake 分别提供 assertSent() 与 assertQueued(),并提供对应的次数断言。测试名称和报告里最好写明所覆盖的是“发送调用”还是“排队调用”。Laravel 13 队列文档

验收目标 应选的断言或检查 能支持的结论 不能据此推断
同步调用发送 Mail::assertSent() 指定 Mailable 被应用要求发送;可按条件核对 外部服务已接收,或收件箱已出现
Mailable 进入邮件队列 Mail::assertQueued() 指定 Mailable 被应用要求排队 队列工作进程已处理,或邮件已发出
检查队列任务派发 Queue::fake() 与对应的队列断言 应用尝试派发预期任务 后台工作进程已执行任务
验证真实收件 受控收件箱检查 本次测试实际观察到收件结果 其他环境、服务或地址也必然成功

需区分两层队列断言:Mail::assertQueued() 检查邮件是否作为排队邮件被记录;若应用先派发一个自定义队列任务,再由任务执行发送逻辑,还要单独检查任务的派发和执行路径。Laravel 队列 API 提供任务断言能力,可据此检查任务是否按预期派发。QueueFake API

因此,assertQueued() 通过只能说明邮件进入了预期的应用队列路径。如果队列配置、工作进程、重试或失败处理也是验收范围,必须另设对应检查;不要让一个断言承担它没覆盖的工作。

Laravel Mailables:收件人和内容要单独检查

应用调用了正确的 Mailable,不代表邮件对象本身内容正确。密码重置邮件可能发给正确用户,却带了错误主题;通知邮件也可能成功构造,但 HTML 按钮缺少目标链接。把“调用断言”和“内容断言”分开,能更快定位问题是在业务流程还是邮件模板。

Laravel 邮件文档包含对发件人、收件人、抄送、密送、回复地址和主题的检查,也介绍了 HTML 与纯文本正文断言。Laravel API 文档索引列出了 assertSeeInHtml()、assertSeeInText() 等 Mailable 方法,可用于检查不同正文版本是否包含预期内容。Mailable 断言 API 索引

$mail = new VerifyAccount($user);

$mail->assertTo($user->email);
$mail->assertHasSubject('请验证账户');
$mail->assertSeeInHtml($expectedVerificationUrl);
$mail->assertSeeInText($expectedVerificationUrl);

这能验证对象生成的邮件结构和正文文本,却不等于在邮件客户端里看到的最终渲染效果。不同客户端可能对 HTML、图片、样式和链接展示采取不同处理;内容断言也不会替我们检查真实投递结果。需要验证实际展示时,应在受控收件中打开邮件,并检查链接目标和可读性。

真实收件:什么时候需要测试收件邮箱验收?

如果验收要求只覆盖“注册接口触发邮件”,应用断言通常是合适的;如果要求确认主题、收件人和正文,就增加 Mailable 内容检查。只有当目标包括真实邮件链路、客户端可读性或链接在收件环境中的表现时,才需要受控真实收件。

真实收件验收仍有边界。一次测试只反映该次发送配置和测试地址的观察结果,不能扩展成普遍送达保证。不要用团队成员的长期邮箱做反复回归:测试数据可能混入工作收件箱,重置链接或测试账户通知也可能带入可识别个人的信息。预发环境应使用专门的测试身份,并避免把真实用户资料写入测试邮件。

如果邮件迟迟未出现,先确认测试目标确实是电子邮件,而不是短信或其他验证渠道;再检查地址复制、发送端结果与等待条件,避免连续触发重发。我们整理了验证码收件问题的排查顺序,可用于安排外部收件验收时的检查步骤。对测试账户的身份信息,也可以参考注册隐私自查,避免把真实个人数据带进演示和预发环境。

提醒:“测试收件箱里看到了邮件”是一次收件观察,不是送达率证明。记录测试时用的环境、邮件路径和收件结果,才能在回归失败时分清是业务逻辑、后台队列还是外部收件链路的问题。

按验收目标选择测试组合

使用下面的条件分支,避免把所有邮件风险都塞进同一条 Mail::fake() 测试:

  • 若只需验证业务代码是否要求发送正确类型的邮件,选 Mail::fake() 加 assertSent(),并核对收件人或次数;否则继续判断是否还需验证内容。
  • 若还需验证主题、发件人、正文或链接文本,增加 Laravel Mailables 内容检查;否则无需把内容断言误当作客户端渲染测试。
  • 若邮件预期进入队列,选 assertQueued();若先派发自定义任务,则另外验证队列任务;否则不能从请求成功推断工作进程已执行。
  • 若验收标准要求邮件实际出现在外部收件箱,使用受控测试地址真实收信,并核对邮件可读性与链接;否则不要把外部链路测试混进每条应用单元测试。
  • 若账户需要长期找回、持续收信或接收安全通知,回退到长期可控的邮箱或转发别名,不要把短期测试收件地址当成永久账户入口。

准备执行时,我们通常按这个顺序落地:

  1. 写清测试通过条件:是调用正确、入队正确、内容正确,还是确实收到邮件。
  2. 在注册、密码重置或通知测试中启用 Mail::fake(),明确哪些测试禁止真实外发。
  3. 对同步邮件使用 assertSent();对排队邮件使用 assertQueued(),并按业务预期核对收件人和次数。
  4. 针对 Mailable 单独检查发件人、收件人、主题及 HTML、纯文本正文中的关键内容。
  5. 如果自定义队列任务承载发送逻辑,分别验证任务派发和任务处理,不把入队结果记为发送结果。
  6. 当通过条件包含真实收件时,再向受控地址发出测试邮件,检查是否收到、内容是否可读、链接是否指向预期页面,并记录本次结果。
  7. 将不同层的结果分别标注为“调用”“排队”“内容”或“真实收件”,避免回归报告只写一个含义模糊的“邮件测试通过”。

外部验收若使用测试收件地址,还要按账户后续用途挑选:一次性注册验证和长期账户找回不是同一类需求。临时邮箱与转发别名的选择指南可帮助区分短期测试收件与需要持续找回的地址。无论使用哪种地址,都不应把测试成功写成对所有用户送达的保证。

把真实收件当作补充证据,而不是替代断言

可靠的 Laravel 13 邮件测试,不是把 Mail::fake() 拿掉后就算完成,而是让每条测试只对自己覆盖的证据负责:应用断言看业务调用,队列断言看排队路径,Mailable 检查看邮件结构,受控收件才回答本次邮件是否实际到达。这样失败时能定位层级,也不会把“请求成功”误报成“用户收到”。

若当前工作只是临时注册自家预发环境、验证外部收件流程或收一封测试通知,FwdTmp 可作为受控收件方案之一;即用的临时地址适合不想把测试邮件放进主邮箱的短期检查。它不能保证你的邮件一定投递成功,也不适合需要长期找回或持续收信的重要账户。

Start with less exposure

下一次注册,先从一张干净的收件箱开始。