[ErrorHandler][FrameworkBundle] better error messages in failing tests

This commit is contained in:
Guillaume Pédelagrabe 2020-03-06 11:57:35 +01:00 committed by Fabien Potencier
parent 08bb79b174
commit 0da9469ee2
6 changed files with 61 additions and 15 deletions

View File

@ -14,6 +14,7 @@ CHANGELOG
* Deprecated *not* setting the "framework.router.utf8" configuration option as it will default to `true` in Symfony 6.0
* Added tag `routing.expression_language_function` to define functions available in route conditions
* Added `debug:container --deprecations` option to see compile-time deprecations.
* Made `BrowserKitAssertionsTrait` report the original error message in case of a failure
5.0.0
-----

View File

@ -11,8 +11,10 @@
namespace Symfony\Bundle\FrameworkBundle\Test;
use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\Constraint\LogicalAnd;
use PHPUnit\Framework\Constraint\LogicalNot;
use PHPUnit\Framework\ExpectationFailedException;
use Symfony\Component\BrowserKit\AbstractBrowser;
use Symfony\Component\BrowserKit\Test\Constraint as BrowserKitConstraint;
use Symfony\Component\HttpFoundation\Request;
@ -28,12 +30,12 @@ trait BrowserKitAssertionsTrait
{
public static function assertResponseIsSuccessful(string $message = ''): void
{
self::assertThat(self::getResponse(), new ResponseConstraint\ResponseIsSuccessful(), $message);
self::assertThatForResponse(new ResponseConstraint\ResponseIsSuccessful(), $message);
}
public static function assertResponseStatusCodeSame(int $expectedCode, string $message = ''): void
{
self::assertThat(self::getResponse(), new ResponseConstraint\ResponseStatusCodeSame($expectedCode), $message);
self::assertThatForResponse(new ResponseConstraint\ResponseStatusCodeSame($expectedCode), $message);
}
public static function assertResponseRedirects(string $expectedLocation = null, int $expectedCode = null, string $message = ''): void
@ -46,42 +48,42 @@ trait BrowserKitAssertionsTrait
$constraint = LogicalAnd::fromConstraints($constraint, new ResponseConstraint\ResponseStatusCodeSame($expectedCode));
}
self::assertThat(self::getResponse(), $constraint, $message);
self::assertThatForResponse($constraint, $message);
}
public static function assertResponseHasHeader(string $headerName, string $message = ''): void
{
self::assertThat(self::getResponse(), new ResponseConstraint\ResponseHasHeader($headerName), $message);
self::assertThatForResponse(new ResponseConstraint\ResponseHasHeader($headerName), $message);
}
public static function assertResponseNotHasHeader(string $headerName, string $message = ''): void
{
self::assertThat(self::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHasHeader($headerName)), $message);
self::assertThatForResponse(new LogicalNot(new ResponseConstraint\ResponseHasHeader($headerName)), $message);
}
public static function assertResponseHeaderSame(string $headerName, string $expectedValue, string $message = ''): void
{
self::assertThat(self::getResponse(), new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue), $message);
self::assertThatForResponse(new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue), $message);
}
public static function assertResponseHeaderNotSame(string $headerName, string $expectedValue, string $message = ''): void
{
self::assertThat(self::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue)), $message);
self::assertThatForResponse(new LogicalNot(new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue)), $message);
}
public static function assertResponseHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void
{
self::assertThat(self::getResponse(), new ResponseConstraint\ResponseHasCookie($name, $path, $domain), $message);
self::assertThatForResponse(new ResponseConstraint\ResponseHasCookie($name, $path, $domain), $message);
}
public static function assertResponseNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void
{
self::assertThat(self::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHasCookie($name, $path, $domain)), $message);
self::assertThatForResponse(new LogicalNot(new ResponseConstraint\ResponseHasCookie($name, $path, $domain)), $message);
}
public static function assertResponseCookieValueSame(string $name, string $expectedValue, string $path = '/', string $domain = null, string $message = ''): void
{
self::assertThat(self::getResponse(), LogicalAnd::fromConstraints(
self::assertThatForResponse(LogicalAnd::fromConstraints(
new ResponseConstraint\ResponseHasCookie($name, $path, $domain),
new ResponseConstraint\ResponseCookieValueSame($name, $expectedValue, $path, $domain)
), $message);
@ -124,6 +126,21 @@ trait BrowserKitAssertionsTrait
self::assertThat(self::getRequest(), $constraint, $message);
}
public static function assertThatForResponse(Constraint $constraint, string $message = ''): void
{
try {
self::assertThat(self::getResponse(), $constraint, $message);
} catch (ExpectationFailedException $exception) {
if (($serverExceptionMessage = self::getResponse()->headers->get('X-Debug-Exception'))
&& ($serverExceptionFile = self::getResponse()->headers->get('X-Debug-Exception-File'))) {
$serverExceptionFile = explode(':', $serverExceptionFile);
$exception->__construct($exception->getMessage(), $exception->getComparisonFailure(), new \ErrorException(rawurldecode($serverExceptionMessage), 0, 1, rawurldecode($serverExceptionFile[0]), $serverExceptionFile[1]), $exception->getPrevious());
}
throw $exception;
}
}
private static function getClient(AbstractBrowser $newClient = null): ?AbstractBrowser
{
static $client;

View File

@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Tests\Test;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\ExpectationFailedException;
use PHPUnit\Framework\TestCase;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestAssertionsTrait;
@ -235,6 +236,17 @@ class WebTestCaseTest extends TestCase
$this->getRequestTester()->assertRouteSame('articles');
}
public function testExceptionOnServerError()
{
try {
$this->getResponseTester(new Response('', 500, ['X-Debug-Exception' => 'An exception has occurred', 'X-Debug-Exception-File' => '%2Fsrv%2Ftest.php:12']))->assertResponseIsSuccessful();
} catch (ExpectationFailedException $exception) {
$this->assertSame('An exception has occurred', $exception->getPrevious()->getMessage());
$this->assertSame('/srv/test.php', $exception->getPrevious()->getFile());
$this->assertSame(12, $exception->getPrevious()->getLine());
}
}
private function getResponseTester(Response $response): WebTestCase
{
$client = $this->createMock(KernelBrowser::class);

View File

@ -1,6 +1,11 @@
CHANGELOG
=========
5.1.0
-----
* The `HtmlErrorRenderer` and `SerializerErrorRenderer` add `X-Debug-Exception` and `X-Debug-Exception-File` headers in debug mode.
4.4.0
-----

View File

@ -66,9 +66,13 @@ class HtmlErrorRenderer implements ErrorRendererInterface
*/
public function render(\Throwable $exception): FlattenException
{
$exception = FlattenException::createFromThrowable($exception, null, [
'Content-Type' => 'text/html; charset='.$this->charset,
]);
$headers = ['Content-Type' => 'text/html; charset='.$this->charset];
if (\is_bool($this->debug) ? $this->debug : ($this->debug)($exception)) {
$headers['X-Debug-Exception'] = rawurlencode($exception->getMessage());
$headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine();
}
$exception = FlattenException::createFromThrowable($exception, null, $headers);
return $exception->setAsString($this->renderException($exception));
}

View File

@ -53,14 +53,21 @@ class SerializerErrorRenderer implements ErrorRendererInterface
*/
public function render(\Throwable $exception): FlattenException
{
$flattenException = FlattenException::createFromThrowable($exception);
$headers = [];
$debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception);
if ($debug) {
$headers['X-Debug-Exception'] = rawurlencode($exception->getMessage());
$headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine();
}
$flattenException = FlattenException::createFromThrowable($exception, null, $headers);
try {
$format = \is_string($this->format) ? $this->format : ($this->format)($flattenException);
return $flattenException->setAsString($this->serializer->serialize($flattenException, $format, [
'exception' => $exception,
'debug' => \is_bool($this->debug) ? $this->debug : ($this->debug)($exception),
'debug' => $debug,
]));
} catch (NotEncodableValueException $e) {
return $this->fallbackErrorRenderer->render($exception);