Buy Access to Course
10.

Mad Test Debugging

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

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():

205 lines | src/AppBundle/Test/ApiTestCase.php
// ... lines 1 - 64
protected function onNotSuccessfulTest(Exception $e)
{
if (self::$history && $lastResponse = self::$history->getLastResponse()) {
$this->printDebug('');
$this->printDebug('<error>Failure!</error> when making the following request:');
$this->printLastRequestUrl();
$this->printDebug('');
$this->debugResponse($lastResponse);
}
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().

200 lines | src/AppBundle/Test/ApiTestCase.php
// ... 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:

200 lines | src/AppBundle/Test/ApiTestCase.php
// ... lines 1 - 101
protected function debugResponse(ResponseInterface $response)
{
$this->printDebug(AbstractMessage::getStartLineAndHeaders($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:

200 lines | src/AppBundle/Test/ApiTestCase.php
// ... 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
$programmer->setUser($this->findUserByUsername('weaverryan'));
// ... 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:

209 lines | src/AppBundle/Test/ApiTestCase.php
// ... 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();
}
$this->output->writeln($string);
}
// ... 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.