Laravel中assertDispatched仅在使用event助手时生效的问题排查
嘿,这个问题我之前踩过同款坑,核心原因和Laravel容器的实例绑定时机以及Event::fake()的工作机制直接相关,咱们一步步理清楚:
1. 先搞懂Event::fake()到底做了什么
当你调用Event::fake()时,Laravel会把容器中绑定的EventDispatcher实例替换成一个模拟(Fake)实现。后续所有从容器中获取的事件调度器,都会是这个Fake实例——这也是咱们能通过Event::assertDispatched()断言事件的前提。
2. 你的问题出在哪?
看你的代码逻辑:
- 服务提供者里注册了
CartInterface的单例,用闭包返回SessionCart实例,并且注入了当时容器里的events实例。 - 你的测试类中,
$this->cartService大概率是在setUp()方法中就已经从容器解析出来了(比如通过依赖注入或者提前调用app()),而Event::fake()是在测试方法内部才调用的。
这就导致:cartService实例在Event::fake()之前就被创建了,它持有的是替换前的真实EventDispatcher实例,所以你在服务里用$this->events->fire()触发的事件,走的是真实调度器,根本没经过Fake实例,自然无法被Event::assertDispatched()捕获。
3. 为什么event()助手就能行?
event()助手的本质是每次调用时,都会直接从当前容器中获取events实例。当你调用Event::fake()之后,容器里的events已经被替换成Fake实例了,所以助手触发的事件会经过Fake实例,断言自然能通过。
解决方案:确保服务在Event::fake()之后才被解析
调整你的测试代码,把服务实例的获取放在Event::fake()之后,而不是提前在setUp()中初始化:
public function it_can_add_an_item() { Event::fake(); // 在这里才从容器中解析服务,确保拿到的是注入了Fake事件调度器的实例 $cartService = app(CartInterface::class); $cartService->add(new Item); $this->assertEquals(1, $cartService->count()); Event::assertDispatched('cart.added'); }
如果你的测试类是通过构造函数或者属性注入来获取cartService的,那就要避免在测试方法执行前(比如setUp())解析服务,而是在Event::fake()之后手动从容器获取。
总结
核心原则就是:单例服务的依赖注入时机,必须晚于Event::fake()的调用时机,这样服务才能拿到Fake的事件调度器实例,你的断言才能生效。
内容的提问来源于stack exchange,提问作者rzb

