如何在 PHPUnit 测试中覆盖 Laravel 的异常处理程序


How can I override Laravel's exception handler in PHPUnit tests?

我正在开发一个包含控制器的Laravel软件包,我希望对其进行单元测试。问题是,如果在其中一个控制器中抛出异常,Laravel的异常处理程序会捕获它并输出500 HTTP响应。PHPUnit 并不明智,测试根本无法满足 200 OK 断言。PHPUnit 的输出中缺少堆栈跟踪,无论是在本地还是在 Travis CI 等服务上

,都极大地阻碍了工作流程。

我知道我可以从诸如 'App'Exceptions'Handler 之类的地方重新抛出异常,但由于这是一个包,我无法修改这些应用程序文件(laravel/laravel只是测试的依赖项,以便为测试控制器提供必要的组件(。

我本以为下面的set_exception_handler()调用会起作用TestCase,但奇怪的是它没有任何影响:

public function createApplication()
{
    $app = require __DIR__.'/../bootstrap/app.php';
    $app->make(Illuminate'Contracts'Console'Kernel::class)->bootstrap();
    set_exception_handler(function ($e) {
        throw $e;
    });
    return $app;
}

谁能告诉我为什么上述不起作用?

单元测试应该测试单个单元,而不是整个应用程序。

您很有可能正在进行功能或集成测试。

当您测试控制器并期望 200 响应时,您需要知道的只是响应代码。 500 响应未通过测试,这表示故障或配置错误。CI 工具应仅显示通过/失败的测试数。

测试不是调试,不应提供任何特殊的回溯。如果您觉得没有足够的关于错误性质的信息,则应查看错误处理和日志记录。否则,当生产中发生错误时,您将面临缺乏信息的相同问题。

您尝试重置异常处理程序的尝试将不起作用(正如您已经发现的那样(。当您使用 call() 方法发出请求时,Laravel 会创建 Http 内核的新实例,当它处理新请求时,它会引导自身并运行HandleExceptions引导程序,这当然会再次重置异常处理程序。

您可能无权修改 'App'Exceptions'Handler 类,但您不需要这样做。您需要做的就是创建自己的异常处理程序,然后更新 IoC 容器中的绑定以使用异常处理程序,而不是 Laravel 应用程序提供的处理程序。

因此,首先,创建新的异常处理程序:

<?php
class MyExceptionHandler extends 'App'Exceptions'Handler
{
    public function render($request, 'Exception $e)
    {
        // You may want to keep all these cases for special exceptions.
        // If not, just get rid of these lines.
        $e = $this->prepareException($e);
        if ($e instanceof HttpResponseException) {
            return $e->getResponse();
        } elseif ($e instanceof AuthenticationException) {
            return $this->unauthenticated($request, $e);
        } elseif ($e instanceof ValidationException) {
            return $this->convertValidationExceptionToResponse($e, $request);
        }
        // Not a special exception. Just re-throw it to get meaningful output.
        throw $e;
    }
}

现在,在 createApplication() 方法中,更新异常处理程序绑定:

public function createApplication()
{
    $app = require __DIR__.'/../bootstrap/app.php';
    // Tell the tests to use your exception handler.
    $app->singleton('Illuminate'Contracts'Debug'ExceptionHandler::class, MyExceptionHandler::class);
    $app->make(Illuminate'Contracts'Console'Kernel::class)->bootstrap();
    return $app;
}