Symfony4.4 mock带doctrine.event_subscriber标签服务提示已初始化无法替换
带doctrine.event_subscriber标签的服务,会在Doctrine Bundle初始化阶段被主动实例化,注册到Doctrine事件管理器中,这个执行时机远早于测试用例中调用self::$container->set()替换服务的时机。
Symfony 4.4的依赖注入容器有明确限制:已经完成实例化的服务不允许被替换,因此会抛出服务已初始化,无法替换的异常。
移除标签后该服务可以正常替换,是因为没有了Doctrine启动阶段的主动实例化逻辑,服务会保持未初始化的懒加载状态,直到第一次被业务代码取用,此时你提前调用set()替换mock实例就不会触发限制。
根据你的业务场景,可以从以下三种方案中选择:
方案1:测试环境下禁用事件订阅者标签(推荐)
在测试专用的服务配置文件中,移除该服务的doctrine.event_subscriber标签,同时将服务设为public,避免启动阶段提前实例化:# config/packages/test/services_test.yaml services: Some\Namespace\SomeClass: public: true tags: - { name: doctrine.event_subscriber, enabled: false }配置完成后即可在测试代码中正常替换服务。如果你的mock实例需要响应Doctrine事件,替换服务后手动将mock注册到事件管理器即可:
self::$container->set('some.class', $someMock); $eventManager = self::$container->get('doctrine.orm.default_entity_manager')->getEventManager(); $eventManager->addEventSubscriber($someMock);方案2:直接操作Doctrine事件管理器替换订阅者
不需要修改服务配置,在测试代码中先移除已初始化的原服务实例,再注册mock即可:$eventManager = self::$container->get('doctrine.orm.default_entity_manager')->getEventManager(); $originalSubscriber = self::$container->get('some.class'); // 解绑原实例订阅的所有事件 $eventManager->removeEventListener($originalSubscriber->getSubscribedEvents(), $originalSubscriber); // 注册mock实例 $eventManager->addEventSubscriber($someMock);注意:如果业务代码中存在直接从容器获取
some.class服务的逻辑,该方案下拿到的还是原实例而非mock,这类场景需要搭配方案1使用。方案3:测试环境下开启服务懒加载
在测试环境配置中给服务添加懒加载标记,Doctrine启动时只会拿到服务代理对象,不会触发真实类的实例化,后续替换服务就不会触发已初始化限制:# config/packages/test/services_test.yaml services: Some\Namespace\SomeClass: public: true lazy: true
内容的提问来源于stack exchange,提问作者PianistaMichal

