如何在 PHPUnit 测试中覆盖 Laravel 的异常处理程序?
How can I override Laravel's exception handler in PHPUnit tests?
我正在开发一个包含控制器的 Laravel 包,我希望对其进行单元测试。问题是如果在这些控制器之一中抛出异常,Laravel 的异常处理程序会捕获它并输出 500 HTTP 响应。 PHPUnit none 更聪明,测试根本无法满足 200 OK 断言。本地和 Travis 等服务上的 PHPUnit 输出中缺少堆栈跟踪 CI 极大地阻碍了工作流程。
我知道我可以从某个地方重新抛出异常,例如 \App\Exceptions\Handler
,但由于这是一个包,我无法修改那些应用程序文件(laravel/laravel
只是一个依赖项用于测试,以便为测试控制器添加必要的组件。
我原以为下面 TestCase
中的 set_exception_handler()
调用会起作用,但奇怪的是它没有任何影响:
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 工具应该只显示 passed/failed 测试的数量。
测试不是调试,不应提供任何特殊的回溯。如果您觉得您没有足够的关于错误性质的信息,您应该检查您的错误处理和日志记录。否则,当生产中发生错误时,您将面临同样的信息缺乏问题。
您尝试重置您所做的异常处理程序将不起作用(正如您已经发现的那样)。当您使用 call()
方法发出请求时,Laravel 会创建一个新的 Http 内核实例,当它处理新请求时,它会自行引导并运行 HandleExceptions
引导程序,这当然会再次重置异常处理程序。
您可能无权修改 \App\Exceptions\Handler
class,但您不需要这样做。您需要做的就是创建您自己的异常处理程序,然后更新 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;
}
我正在开发一个包含控制器的 Laravel 包,我希望对其进行单元测试。问题是如果在这些控制器之一中抛出异常,Laravel 的异常处理程序会捕获它并输出 500 HTTP 响应。 PHPUnit none 更聪明,测试根本无法满足 200 OK 断言。本地和 Travis 等服务上的 PHPUnit 输出中缺少堆栈跟踪 CI 极大地阻碍了工作流程。
我知道我可以从某个地方重新抛出异常,例如 \App\Exceptions\Handler
,但由于这是一个包,我无法修改那些应用程序文件(laravel/laravel
只是一个依赖项用于测试,以便为测试控制器添加必要的组件。
我原以为下面 TestCase
中的 set_exception_handler()
调用会起作用,但奇怪的是它没有任何影响:
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 工具应该只显示 passed/failed 测试的数量。
测试不是调试,不应提供任何特殊的回溯。如果您觉得您没有足够的关于错误性质的信息,您应该检查您的错误处理和日志记录。否则,当生产中发生错误时,您将面临同样的信息缺乏问题。
您尝试重置您所做的异常处理程序将不起作用(正如您已经发现的那样)。当您使用 call()
方法发出请求时,Laravel 会创建一个新的 Http 内核实例,当它处理新请求时,它会自行引导并运行 HandleExceptions
引导程序,这当然会再次重置异常处理程序。
您可能无权修改 \App\Exceptions\Handler
class,但您不需要这样做。您需要做的就是创建您自己的异常处理程序,然后更新 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;
}