Mad Test Debugging

When we mess up in a web app, we see Symfony's giant exception page. I want that same experience when I'm building an API.

At the root of the project there's a resources/ directory with an ApiTestCase.php file. This has all the same stuff as our ApiTestCase plus some pretty sweet new debugging stuff.

Copy this and paste it over our class.

First, check out onNotSuccessfulTest():

... lines 1 - 64
protected function onNotSuccessfulTest(Exception $e)
if (self::$history && $lastResponse = self::$history->getLastResponse()) {
$this->printDebug('<error>Failure!</error> when making the following request:');
throw $e;
... lines 78 - 205

If you have a method with this name, PHPUnit calls it whenever a test fails. I'm using it to print out the last response so we can see what just happened.

I also added a few other nice things, like printLastRequestUrl().

... lines 1 - 90
protected function printLastRequestUrl()
$lastRequest = self::$history->getLastRequest();
if ($lastRequest) {
$this->printDebug(sprintf('<comment>%s</comment>: <info>%s</info>', $lastRequest->getMethod(), $lastRequest->getUrl()));
} else {
$this->printDebug('No request was made.');
... lines 101 - 200

Next up is debugResponse() use it if you want to see what a Response looks like:

... lines 1 - 101
protected function debugResponse(ResponseInterface $response)
$body = (string) $response->getBody();
... lines 106 - 172
... lines 174 - 200

This crazy function is something I wrote - it knows what Symfony's error page looks like and tries to extract the important parts... so you don't have to stare at a giant HTML page in your terminal. I hate that. It's probably not perfect - and if you find an improvement and want to share it, you'll be my best friend.

And finally, whenever this class prints something, it's calling printDebug(). And right now, it's about as dull as you can get:

... lines 1 - 179
protected function printDebug($string)
echo $string."\n";
... lines 184 - 200

I think we can make that way cooler. But first, with this in place, it should print out the last response so we can see the error:

php bin/phpunit -c app src/AppBundle/Tests/Controller/Api/ProgrammerControllerTest.php

Ah hah!

Catchable Fatal Error: Argument 1 passed to Programmer::setUser() must
be an instance of AppBundle\Entity\User, null given in ProgrammerController.php
on line 29.

So the problem is that when we delete our database, we're also deleting our hacked-in weaverryan user:

... lines 1 - 17
public function newAction(Request $request)
... lines 20 - 23
... lines 25 - 30
... lines 32 - 33

Let's deal with that in a second - and do something cool first. So, remember how some of the app/console commands have really pretty colored text when they print? Well, we're not inside a console command in PHPUnit, but I'd love to be able to print out with colors.

Good news! It turns out, this is really easy. The class that handles the styling is called ConsoleOutput, and you can use it directly from anywhere.

Start by adding a private $output property that we'll use to avoid creating a bunch of these objects. Then down in printDebug(), say if ($this->output === null) then $this->output = new ConsoleOutput();. This is the $output variable you're passed in a normal Symfony command. This means we can say $this->output->writeln() and pass it the $string:

... lines 1 - 12
use Symfony\Component\Console\Output\ConsoleOutput;
... lines 14 - 15
class ApiTestCase extends KernelTestCase
... lines 18 - 29
* @var ConsoleOutput
private $output;
... lines 34 - 184
protected function printDebug($string)
if ($this->output === null) {
$this->output = new ConsoleOutput();
... lines 193 - 207

I'm coloring some things already, so let's see this beautiful art! Re-run the test:

php bin/phpunit -c app src/AppBundle/Tests/Controller/Api/ProgrammerControllerTest.php

Hey! That error is hard to miss!

Seeing the Exception Stacktrace!

Ok, one more debugging trick. What if we really need to see the full stacktrace? The response headers are printed on top - and one of those actually holds the profiler URL for this request. And to be even nicer, my debug code is printing that at the bottom too.

Pop that into the browser. This is the profiler for that API request. It has cool stuff like the database queries, but most importantly, there's an Exception tab - you can see the full, beautiful exception with stacktrace. This is huge.

Leave a comment!

  • 2018-07-18 Diego Aguiar

    Hey Coder

    You can find those debugging methods inside src/AppBundle/Test/ApiTestCase.php


  • 2018-07-18 Coder

    where can we get that function which extract info from error with html ?

  • 2018-06-11 Victor Bocharsky

    Hey Shaun,

    First of all, try to clear the cache. Also, make sure you use the correct namespace of the Route, it should be:

    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

    And try to debug a bit with "bin/console debug:router", do you see this route in the list?


  • 2018-06-08 Shaun

    Thanks Victor Bocharsky

    The issue I am now having is that because I am using Symfony 4, the route for the API is not being picked up and I am getting following error:

    HTML Summary (h1 and h2):
    Symfony Exception
    ResourceNotFoundException NotFoundHttpException
    HTTP 404 Not Found
    No route found for "POST /app_test.php/api/programmers"

  • 2018-06-08 Victor Bocharsky

    Hey Shaun,

    The message is clear enough - method's signature was changed, so you need to change "onNotSuccessfulTest(Exception $e)" to "onNotSuccessfulTest(Throwable $t)" as in parent class.

    About the second problem, try to sync vendor/ directory, i.e. right click on vendor/ dir and select "Synchronize 'vendor'". If it does not help, try to re-execute "composer install" or remove vendor/ dir manually and re-run "composer install" again.


  • 2018-06-07 Shaun

    I downloaded the ApiTestCase from episode 4 as I am using Guzzle veriosn 6, however when I run PHPUnit test I get the following error:

    Fatal error: Uncaught Declaration of AppBundle\Test\ApiTestCase::onNotSuccessfulTest(Exception $e) should be compatible with PHPUnit\Framework\TestCase::onNotSuccessfulTest(Throwable $t)

    And I am also getting the angry errors from PHPStorm regarding the Guzzle paths ;)

    use GuzzleHttp\Message\AbstractMessage;
    use GuzzleHttp\Message\ResponseInterface;
    use GuzzleHttp\Subscriber\History;

  • 2018-02-13 weaverryan

    Hey Nacer!

    Ah yes. So, this tutorial uses version *5* of Guzzle. But, before episode 4 (, we upgraded to Guzzle 6. If you download the start code for that tutorial, you'll find an ApiTestCase that works with version 6 :).


  • 2018-02-13 Nacer

    I'm on GuzzleHttp 6, I' think the file in resources need to be updated

  • 2018-02-12 Victor Bocharsky

    Hey Nacer,

    What do you mean? Can't PhpStorm find these classes or these classes just are not used in the class? If the 2nd, i.e. you don't use those classes - just remove the namespaces. But if the first one - please, make sure you've installed dependencies with "composer install". Also, what version of Guzzle do you use? Probably you use a different major version, so those namespaces were changed and you need to use the new ones.


  • 2018-02-11 Nacer

    Hi all, my Phpstrom don't like these lines in ApiTestCase calss

    use GuzzleHttp\Message\AbstractMessage;
    use GuzzleHttp\Message\ResponseInterface;
    use GuzzleHttp\Subscriber\History;

  • 2016-12-19 weaverryan

    Hey emm!

    Haha, for the purposes of learning the REST stuff, I would probably not bother upgrading. However, we actually *do* upgrade to Symfony 3 and Guzzle 6 between episode 3 and 4... so episode 4 has all the latest stuff (if you download its course code): So, if you want, that should make upgrading your ApiTestCase to Guzzle 6 pretty easy :).


  • 2016-12-19 emm

    Hi there!! So.. when i use guzzlehttp/guzzle v6.* the code of resource/ApiTesterCase(from the start directory) makes phpStorm angry :P. i read some documentation as of the upgrades of guzzlehttp v5 and v6 but instead of fixing/changing the code i just downgraded the package to v 5. Should i bother fixing the changes to the current version for now? I'm not sure if this is a real or rehtorical question..

    thanks in advance.

  • 2016-08-01 weaverryan

    Hi there!

    Yea, great question! This is a personal preference of mine. The reason is that Guzzle is the standard in the PHP world for making HTTP/API requests. Symfony's client/crawler is quite good, but the crawler (specifically) is useful for crawling HTML pages - it doesn't serve you any purpose when making API requests. So, I usually think it makes more sense to use Guzzle, since you'll probably also use it in the real-world to make API requests to other services.

    However, there is one advantage that Symfony's client has over Guzzle. Because (with the Symfony client) you are *not* making real HTTP requests (you are making "fake" requests into Symfony's kernel), you can potentially do 2 interesting things. First, you could change some setting in the container right in your test class, make the request class, and your code will use that setting. Second, and probably more interesting, you can turn on the profiler and get information from the profiler (e.g. "was an email sent?"). That's not enough for me to want to use it however :).

    Thanks for the question - hope that clarifies!


  • 2016-07-30 Lipiluk

    I would like to ask, why are you using Guzzle instead of in-built Symfony's client/crawler? Are there any advantages or disadvantages of using those both? As far as I can see Symfony's offer programmers such an ability. Thank you for any reply.