You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel测试:如何在单个测试中验证POST请求触发邮件发送

问题

如何在单个Laravel测试中验证以下逻辑:当发送携带有效数据的POST请求时,邮件是否已发送?

现有代码实现:

控制器代码

// controller 
public function store(StoreOrderRequest $request)
{
    OrderCreated::dispatch($request->validated());
}

监听器代码

实现ShouldQueue接口、队列连接为database的SendEmailToUser监听器监听OrderCreated事件:

// SendEmailToUser listener
public function handle(OrderCreated $event)
{
    Mail::send(new OrderCreatedMail($event->data));
} 

目前已有两个可正常运行的测试:

事件分发测试

test('event dispatched', function () {
    Event::fake();

    $this->post(...);

    Event::assertDispatched(OrderCreated::class);
    Event::assertListening(OrderCreated::class, SendEmailToUser::class);
});

邮件发送测试

test('mail sent', function () {
    Mail::fake();

    (new SendEmailToUser())->handle(new OrderCreated($data));

    Mail::assertSent(OrderCreatedMail::class);
});

但希望将它们重构为单个测试,形式如下:

Something::fake();

// When I make a POST request
$this->post(...);

// Email was sent
Mail::assertSent(OrderCreatedMail::class);

核心需求是测试不依赖内部黑盒逻辑,仅关注结果。例如后续若修改邮件发送实现(如直接在控制器发送),原有测试不会失效,只要业务逻辑「发送有效数据的POST请求则发送邮件」成立即可。

解决方案

要实现这个需求,关键是确保队列任务(如果用了队列)能在测试中同步执行,同时伪造邮件服务以拦截实际发送并完成断言。以下是两种可行方案:

方案1:临时切换队列驱动为同步(推荐)

通过临时将队列驱动改为sync,让队列任务在请求后立即执行,配合伪造的邮件服务捕获发送行为:

test('valid post request triggers email', function () {
    // 伪造邮件服务,避免实际发送邮件
    Mail::fake();

    // 临时将队列驱动设为同步,确保队列任务立即执行
    $originalQueueDriver = config('queue.default');
    config(['queue.default' => 'sync']);

    // 准备符合验证规则的测试数据
    $validData = [
        // 填入你的请求字段及有效值,比如 'email' => 'test@example.com'
    ];

    // 发送POST请求
    $this->post(route('orders.store'), $validData);

    // 断言目标邮件已被触发发送
    Mail::assertSent(OrderCreatedMail::class);

    // 恢复原队列驱动(可选,测试结束后Laravel会自动重置配置)
    config(['queue.default' => $originalQueueDriver]);
});

方案2:手动执行队列任务

如果不想修改队列驱动,可以在发送请求后手动执行一次队列任务,处理刚推送的监听器任务:

test('valid post request triggers email', function () {
    Mail::fake();

    $validData = [/* 符合验证规则的测试数据 */];
    $this->post(route('orders.store'), $validData);

    // 执行一次队列任务,处理当前队列中的监听器任务
    $this->artisan('queue:work --once --connection=database');

    // 断言邮件已发送
    Mail::assertSent(OrderCreatedMail::class);
});

方案优势

这两种方案都只关注业务结果:无论后续邮件发送逻辑是移到控制器、更换事件监听器,还是调整队列配置,只要「有效POST请求触发邮件」的业务逻辑成立,测试就能通过,完全避免了对内部实现的依赖。

内容的提问来源于stack exchange,提问作者Ihar Aliakseyenka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 14:16:32