要验证 laravel 8 批处理生命周期事件,需强制同步执行:设置 queue.default 为 sync,所有 job 实现 shouldqueue,用静态日志断言触发顺序——成功时为 ['handled','handled','then','finally'],失败时为 ['catch','finally']。

要验证 Laravel 8 中任务批处理(Batch)的 then、catch、finally 生命周期事件是否被正确触发,必须在单元测试中绕过队列执行机制,直接同步运行批处理及其回调,否则事件不会在当前测试进程内发生。
创建可测试的批处理 Job 类
新建一个轻量级 Job,仅记录状态到内存数组,不依赖数据库或外部服务:
执行 php artisan make:job RecordBatchStatus,然后编辑 app/Jobs/RecordBatchStatus.php:
在 handle() 方法中写入:$this->record('handled');,其中 record() 是一个静态方法,将字符串追加到 self::$log[] 数组中。
【关键前提】 所有 Job 必须实现 ShouldQueue 接口,否则 Bus::batch() 会拒绝接收——这是 Laravel 的硬性校验,不满足则抛出 InvalidArgumentException。
禁用队列驱动,强制同步执行
在 tests/Unit/BatchLifecycleTest.php 开头,添加配置覆盖:
$this->setUpDatabase(); 不需要;但必须调用 $this->withoutExceptionHandling(); 避免因未捕获异常中断测试流程。
在 setUp() 方法中插入:Config::set('queue.default', 'sync'); ——这会让所有 dispatch() 立即执行,而非推入 Redis 或 Database 队列。
若漏掉这步,then() 回调将在另一个独立的队列 Job 中运行,当前测试进程完全无法感知,断言必然失败。
编写断言验证生命周期事件触发顺序
方法一:使用静态日志数组捕获执行痕迹
① 在测试方法开头清空日志:RecordBatchStatus::$log = [];
② 构建批处理并立即 dispatch:$batch = Bus::batch([new RecordBatchStatus, new RecordBatchStatus])->then(fn() => RecordBatchStatus::record('then'))->catch(fn() => RecordBatchStatus::record('catch'))->finally(fn() => RecordBatchStatus::record('finally'))->dispatch();
③ 断言全部 Job 先执行,再触发 then,最后执行 finally:$this->assertEquals(['handled', 'handled', 'then', 'finally'], RecordBatchStatus::$log);
方法二:模拟失败路径验证 catch 触发
替换第一个 Job 为故意抛异常的版本:new class implements ShouldQueue { public function handle() { throw new \Exception('Simulated failure'); } }
此时 then() 不应写入日志,而 catch() 和 finally() 必须写入 —— 注意 catch 回调接收两个参数:(Batch $batch, Throwable $e),测试中可忽略 $batch,只断言 $e->getMessage() 是否匹配。
【不可逆操作】 若在 catch() 回调里调用了 $batch->cancel(),后续 finally() 仍会执行,但批处理状态会被标记为 cancelled,影响后续查询逻辑。











