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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 14:35:04