如何在Laravel测试中断言邮件是否已发送? (Mail::fake与assertSent)

Mail::fake() 必须在被测代码执行前调用,否则 assertSent() 无法捕获邮件;断言时需传入完整命名空间的 ::class 常量,内容校验需通过闭包访问 $mail 实例属性。

如何在laravel测试中断言邮件是否已发送? (mail::fake与assertsent)

Mail::fake() 之后为什么 assertSent() 总是失败?

核心原因:没在测试开始时调用 Mail::fake(),或调用位置不对。Laravel 的邮件门面(Mail)是单例,必须在被测代码执行前「替换」掉真实发送器,否则真实邮件驱动仍会尝试连接 SMTP,而 assertSent() 只能捕获 fake 驱动记录的邮件。

  • Mail::fake() 必须放在测试方法开头,或在 setUp() 中统一调用
  • 若被测逻辑里有 Mail::to(...)->send(...),但 Mail::fake() 写在 send 之后,断言必然失败
  • 使用 Mail::fake(['log']) 等指定驱动时,需确保与配置一致;默认用 array 驱动,已足够断言

assertSent() 和 assertNotSent() 的参数怎么写?

这两个方法接受类名字符串、闭包或 Mailable 实例。最常用的是传入 Mailable 类名 —— 但注意:必须是完整命名空间路径,且不能带 .php 后缀或引号外的斜杠。

  • ✅ 正确:Mail::assertSent(App/Mail/WelcomeEmail::class)
  • ❌ 错误:Mail::assertSent('App/Mail/WelcomeEmail')(字符串形式不支持自动解析)
  • ✅ 带条件断言:Mail::assertSent(App/Mail/WelcomeEmail::class, function ($mail) { return $mail->hasTo('user@example.com'); })
  • ✅ 断言未发送:Mail::assertNotSent(App/Mail/PasswordReset::class)

如何验证邮件内容(收件人、主题、变量)?

assertSent() 本身不校验内容,得通过闭包参数拿到实际构建的 Mailable 实例再检查。关键点在于:Mailable 对象在断言时已执行完 build(),所有 $this->with() 或构造注入的数据都可直接访问。

Mail::assertSent(App/Mail/InvoiceShipped::class, function ($mail) use ($invoice) {
    return $mail->hasTo($invoice->user->email)
        && $mail->subject === 'Your Invoice #'.$invoice->number
        && $mail->invoice->id === $invoice->id;
});
  • $mail->hasTo() 支持邮箱字符串、User 模型或集合,比手动查 $mail->to 数组更可靠
  • 主题字段是 $mail->subject(不是 $mail->getSubject()),因为 Laravel 在 build() 中已赋值
  • 传递给 Mailable 构造函数的数据(如 new InvoiceShipped($invoice))会作为公共属性挂载,可直接读取

测试队列邮件时 fake 还管用吗?

管用,但必须确保队列任务真正执行了。Laravel 默认测试中队列是同步模式(sync),只要没显式改成 databaseredisMail::fake() 就能捕获到延迟发送的邮件。

  • 确认 QUEUE_CONNECTION=syncphpunit.xml 或测试环境配置中生效
  • 如果用了 dispatch(new SendInvoiceEmail($invoice))->delay(now()->addSeconds(1)),需调用 Mail::assertSent() 前让队列运行:Queue::assertPushed(SendInvoiceEmail::class) 不等于邮件已发,要等任务执行完毕
  • 更稳妥做法:在测试中用 Bus::fake()Queue::fake() 配合 Mail::fake(),然后手动调用 app()->make(SendInvoiceEmail::class)->handle() 绕过队列

Laravel 的邮件 fake 机制很轻量,但容易卡在「调用时机」和「类引用方式」上;一旦断言失败,优先检查 Mail::fake() 是否真正在发送前生效,以及传给 assertSent() 的是不是 ::class 常量。

https://www.php.cn/faq/2000125.html

发表回复

Your email address will not be published. Required fields are marked *