Symfony测试替换已初始化RouterInterface服务报错求助
问题:Mock RouterInterface时因Monolog处理器提前初始化导致失败
原本测试中可以正常Mock RouterInterface,但添加依赖该接口的Monolog处理器后,测试报错:The "test.Symfony\Component\Routing\RouterInterface" service is already initialized, you cannot replace it.,原因是客户端创建时,Monolog处理器已提前初始化并注入了原始的Router服务,导致后续无法替换为Mock对象。
原测试代码
public function testRouteNotFound(): void { $client = self::createClient(); $container = $client->getContainer(); assert($container instanceof ContainerInterface); $container->set( 'test.' . RouterInterface::class, Mockery::mock( RouterInterface::class, static function (MockInterface $mock): void { $mock->shouldReceive('getRouteCollection')->andReturn(new RouteCollection()); }, )->makePartial(), ); // 请求触发与断言逻辑 }
原测试环境配置(config/packages/test/service.yaml)
services: test.Symfony\Component\Routing\RouterInterface: alias: Symfony\Component\Routing\RouterInterface public: true
新增的Monolog处理器
final class RequestDataProcessor implements ProcessorInterface { public function __construct(private readonly RouterInterface $router) { } public function __invoke(array $record): array { if (PHP_SAPI === 'cli') { return $record; } $record['route'] = $this->router->match('some_route'); $context = []; // 业务相关上下文数据 return $record + $context; } }
解决方案
1. 调整顺序:先Mock服务再创建客户端
核心思路是在客户端初始化前就把Mock好的Router注入容器,避免Monolog处理器提前加载原始服务。
修改测试代码:
public function testRouteNotFound(): void { // 1. 提前创建Mock对象 $mockRouter = Mockery::mock(RouterInterface::class) ->shouldReceive('getRouteCollection') ->andReturn(new RouteCollection()) ->getMock(); // 2. 将Mock注入容器(直接使用原始服务ID,无需前缀) self::getContainer()->set(RouterInterface::class, $mockRouter); // 3. 再创建客户端 $client = self::createClient(); // 后续请求触发与断言逻辑 }
同时简化测试环境的service.yaml配置:
# config/packages/test/service.yaml services: Symfony\Component\Routing\RouterInterface: public: true
2. 让Monolog处理器延迟加载RouterInterface
通过延迟获取Router服务,避免在客户端初始化时就加载原始服务。有两种实现方式:
方式A:注入容器按需获取
修改处理器代码:
final class RequestDataProcessor implements ProcessorInterface { private ?RouterInterface $router = null; public function __construct(private readonly ContainerInterface $container) { } private function getRouter(): RouterInterface { if (!$this->router) { $this->router = $this->container->get(RouterInterface::class); } return $this->router; } public function __invoke(array $record): array { if (PHP_SAPI === 'cli') { return $record; } $record['route'] = $this->getRouter()->match('some_route'); $context = []; return $record + $context; } }
方式B:使用Symfony懒加载服务
在services.yaml中给处理器配置懒加载,Symfony会自动生成代理类延迟初始化依赖:
# config/services.yaml App\Logger\RequestDataProcessor: tags: - { name: monolog.processor, handler: main } arguments: $router: '@Symfony\Component\Routing\RouterInterface' lazy: true
3. 测试环境替换Monolog处理器
直接在测试环境中把处理器换成无依赖的测试实现或Mock,彻底避免加载Router服务。
方式A:创建测试用空实现
先创建测试专用处理器:
// src/Logger/Test/RequestDataProcessor.php namespace App\Logger\Test; use Monolog\Processor\ProcessorInterface; final class RequestDataProcessor implements ProcessorInterface { public function __invoke(array $record): array { // 测试环境无需处理日志,直接返回原记录 return $record; } }
然后在测试环境配置中替换:
# config/packages/test/services.yaml App\Logger\RequestDataProcessor: class: App\Logger\Test\RequestDataProcessor tags: - { name: monolog.processor, handler: main }
方式B:直接Mock处理器
如果不需要处理器的任何逻辑,也可以直接在测试配置中Mock:
# config/packages/test/services.yaml App\Logger\RequestDataProcessor: class: Mockery\MockInterface factory: ['Mockery', 'mock'] arguments: [Monolog\Processor\ProcessorInterface] tags: - { name: monolog.processor, handler: main }
内容的提问来源于stack exchange,提问作者Bogdan Dubyk
相关产品推荐
相关产品推荐

