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

Symfony3.4单元测试Mock服务异常:断言信息混入响应输出

问题:Symfony控制器单元测试中Mock Logger的错误信息无法在CLI正常显示

背景代码

控制器代码

我有一个以服务形式存在的DefaultController,依赖注入为自动注入模式,代码如下:

class DefaultController extends BaseController {
    private $logger;
    private $recordRepository;
    public function __construct(LoggerInterface $logger, RecordRepository $recordRepository) {
        $this->logger = $logger;
        $this->recordRepository = $recordRepository;
    }
    public function downloadAction($uuid) {
        $recordRepo = $this->recordRepository;
        $record = $recordRepo->findActive($uuid);
        $logger = $this->logger;
        if (!$record) {
            $logger->error('Record not found: '.$uuid);
            return $this->render("default/download_error.html.twig", [
                'error' => 'record_not_found'
            ]);
        }
    }
}

单元测试代码

我编写了一个单元测试,尝试调用该方法并将Logger服务替换为Mock对象,测试代码如下:

<?php
namespace Tests\AppBundle\Controller;
use Monolog\Logger;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class DefaultControllerTest extends WebTestCase {
    public function testDownloadAction() {
        $mock = $this->getMockBuilder(Logger::class)
            ->disableOriginalConstructor()
            ->getMock();
        $mock->expects($this->once())->method('error')
            ->with('Record not found: 1223');
        $client = static::createClient([]);
        $client->getContainer()->set('logger', $mock);
        $client->request('GET', '/download/12233');
        print $client->getResponse()->getContent();
        $this->assertEquals(200, $client->getResponse()->getStatusCode());
    }
}

遇到的问题

当测试失败(Logger的error方法调用参数与预期不匹配)时,PHPUnit的错误信息被放入$client->getResponse的输出中,而非在CLI中直接显示。CLI输出只显示错误信息以500页面形式返回,最终PHPUnit仅断言响应状态码为200失败,看不到真正的Mock验证失败原因。请问我是否需要使用普通PHPUnit测试用例来使用Mock对象?如果是,是否需要重构控制器中的$this->render方法?


解决方案分析

为什么会出现这个问题

你用的WebTestCase会启动Symfony内核,而内核默认会捕获所有异常并转换成HTTP 500响应页面。当你的Mock Logger验证失败时,PHPUnit抛出的ExpectationFailedException被内核捕获,变成了500错误页面,导致你在CLI里看不到真正的失败原因,只能看到状态码断言失败。

方案1:改用普通PHPUnit测试(推荐)

不需要依赖WebTestCase,直接实例化控制器并手动注入Mock依赖,这样测试是直接调用代码逻辑,不走HTTP请求流程,异常会直接在CLI抛出,能看到完整的Mock验证失败信息。

示例代码如下:

<?php
namespace Tests\AppBundle\Controller;

use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use AppBundle\Controller\DefaultController;
use AppBundle\Repository\RecordRepository;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;
use Twig\Environment;

class DefaultControllerTest extends TestCase
{
    public function testDownloadActionWhenRecordNotFound(): void
    {
        // 1. Mock Logger,设置预期调用
        /** @var MockObject&LoggerInterface $loggerMock */
        $loggerMock = $this->createMock(LoggerInterface::class);
        $loggerMock->expects($this->once())
            ->method('error')
            ->with('Record not found: 1223');

        // 2. Mock RecordRepository,让它返回null模拟记录不存在
        /** @var MockObject&RecordRepository $repoMock */
        $repoMock = $this->createMock(RecordRepository::class);
        $repoMock->method('findActive')
            ->with('1223')
            ->willReturn(null);

        // 3. Mock Twig环境,因为控制器要调用render方法
        /** @var MockObject&Environment $twigMock */
        $twigMock = $this->createMock(Environment::class);
        $twigMock->method('render')
            ->with('default/download_error.html.twig', ['error' => 'record_not_found'])
            ->willReturn('<html>Record not found error</html>');

        // 4. 实例化控制器并注入所有Mock依赖
        $controller = new DefaultController($loggerMock, $repoMock);
        // 给控制器设置Twig(根据你的BaseController实现调整,比如如果有$twig属性或者setter)
        $controller->twig = $twigMock;

        // 5. 直接调用action方法
        $response = $controller->downloadAction('1223');

        // 6. 断言响应结果
        $this->assertEquals(200, $response->getStatusCode());
        $this->assertStringContainsString('record_not_found', $response->getContent());
    }
}

方案2:修改WebTestCase配置,让内核不捕获异常

如果你还是想保留HTTP请求式的测试,可以让Symfony内核在测试环境下不捕获异常,这样PHPUnit的Mock验证异常会直接抛出到CLI。

方法A:创建客户端时临时设置

$client = static::createClient([], [
    'error_handler' => [
        'throw_exception' => true,
    ],
]);

方法B:全局配置测试环境

在config/packages/test/framework.yaml中添加:

framework:
    error_handler:
        throw_exception: true

这样所有测试用例都会直接抛出异常,而不是转换成500页面。不过这种方式可能会让一些HTTP层面的错误也直接抛出,需要根据你的测试需求权衡。

关于是否需要重构render方法

不需要强制重构!只要你能正确Mock控制器依赖的Twig环境,就可以正常测试。当然,如果后续想让控制器更易测试,可以考虑把响应生成逻辑(比如渲染模板)抽象成独立的服务,但当前场景下完全不需要大改。

内容的提问来源于stack exchange,提问作者Oli

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:40:38