Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

The ResponseAsserter!

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

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

Login Subscribe

In every test, we're going to decode the JSON response and then assert some stuff - like "does the nickname property exist?" or "is it equal to UnitTester?"

Find the resources directory at the root of your project. I'm switching my PhpStorm mode up here temporarily, because I marked this directory as "excluded" so that Storm wouldn't try to autocomplete from stuff in it. See that ResponseAsserter.php file? Yea, copy that - it's good stuff.

Paste it into the Test directory right next to ApiTestCase. And now I'll re-hide that resources folder in PhpStorm.

Hello ResponseAsserter! This class is really good at reading properties off of a JSON response:

... lines 1 - 13
class ResponseAsserter extends \PHPUnit_Framework_Assert
{
... lines 16 - 179
}

We won't read through this now, but you should. It uses the same PropertyAccess component internally - and we'll use its superpowers through this.

Setting things up in ApiTestCase

To use this in ApiTestCase, create a new private property called $responseAsserter:

... lines 1 - 19
class ApiTestCase extends KernelTestCase
{
... lines 22 - 43
private $responseAsserter;
... lines 45 - 270
}

And then way down at the bottom - make a protected function asserter(). We'll use the property to avoid making multiple asserters. So, if $this->responseAsserter === null then set that to a new ResponseAsserter(). Finish by returning it:

... lines 1 - 19
class ApiTestCase extends KernelTestCase
{
... lines 22 - 251
/**
* @return ResponseAsserter
*/
protected function asserter()
{
if ($this->responseAsserter === null) {
$this->responseAsserter = new ResponseAsserter();
}
return $this->responseAsserter;
}
... lines 263 - 270
}

Assert!

Now let's use this! Instead of having Guzzle decode the JSON for us, we can just say $this->asserter()->responsePropertiesExist() and pass it the $response we want it to look at and the array of properties that should exist in its JSON:

... lines 1 - 35
public function testGETProgrammer()
{
... lines 38 - 42
$response = $this->client->get('/api/programmers/UnitTester');
$this->assertEquals(200, $response->getStatusCode());
$this->asserter()->assertResponsePropertiesExist($response, array(
'nickname',
'avatarNumber',
'powerLevel',
'tagLine'
));
... line 51
}
... lines 53 - 72

That gets rid of a nice block of code. Inside the new function, it just loops over each property and reads their value using the PropertyAccess component. And it is still just using json_decode internally. It's just an easier way to look into the JSON response.

Since we're responsible coders, let's assert that it all works:

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

Excellent! Let's add one more - an assert that the nickname is set to UnitTester. Use assertResponsePropertyEquals() - always pass the $response first. Then, nickname and it should equal UnitTester:

... lines 1 - 35
public function testGETProgrammer()
{
... lines 38 - 50
$this->asserter()->assertResponsePropertyEquals($response, 'nickname', 'UnitTester');
}
... lines 53 - 72

Run that!

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

And that passes. Nothing scares me more than when things are green on the first try, ... well that and snakes on a plane. So, let's assert UnitTester2 and see it fail.

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

Phew, ok good!

Property "nickname": Expected "UnitTester2" but response was "UnitTester"

And like all failures, it prints out the raw response above this.

Testing /api/programmers

Our testing setup is, well, pretty sweet. So testing the GET collection endpoint should be easy. Create a testGETProgrammersCollection() method. Grab the createProgrammer() code from above, but paste it twice to create a new CowboyCoder:

... lines 1 - 53
public function testGETProgrammersCollection()
{
$this->createProgrammer(array(
'nickname' => 'UnitTester',
'avatarNumber' => 3,
));
$this->createProgrammer(array(
'nickname' => 'CowboyCoder',
'avatarNumber' => 5,
));
... lines 64 - 69
}
... lines 71 - 72

Now grab the lines that makes the request and asserts the status code. Update the URL to just /api/programmers:

... lines 1 - 53
public function testGETProgrammersCollection()
{
... lines 56 - 64
$response = $this->client->get('/api/programmers');
$this->assertEquals(200, $response->getStatusCode());
... lines 67 - 69
}
... lines 71 - 72

No assertions yet, but let's make sure it doesn't blow up:

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

Great! Ok, so what do we want to assert? I don't know, what do you want to assert? Think about how the endpoint works: we're returning an associative array with a programmers key and that actually holds the collection of programmers:

... lines 1 - 72
public function listAction()
{
... lines 75 - 77
$data = array('programmers' => array());
foreach ($programmers as $programmer) {
$data['programmers'][] = $this->serializeProgrammer($programmer);
}
$response = new JsonResponse($data, 200);
return $response;
}
... lines 87 - 98

Let's first assert that there's a programmers key in the response and that it's an array. Use $this->asserter()->assertResponsePropertyIsArray(): pass it the $response and the property: programmers. Next, let's assert that there are two things on this array. There's a method for that called assertResponsePropertyCount() - pass it the $response, programmers and the number 2:

... lines 1 - 53
public function testGETProgrammersCollection()
{
... lines 56 - 64
$response = $this->client->get('/api/programmers');
$this->assertEquals(200, $response->getStatusCode());
$this->asserter()->assertResponsePropertyIsArray($response, 'programmers');
$this->asserter()->assertResponsePropertyCount($response, 'programmers', 2);
... line 69
}
... lines 71 - 72

Now let's run this - but copy the method name first. On the command line, before the filename, add --filter then paste the method name to just run this test:

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

Yep - one little dot and the PHPUnit gnomes are pleased.

Deep Assertions with Property Path

Let's go further. We know the programmers property will have 2 items in it: the 0 index should be the UnitTester data and the 1 index should be the CowboyCoder data. Copy the assertResponsePropertyEquals() method and paste it here. But instead of just nickname, use programmers[1].nickname. And this should be CowboyCoder:

... lines 1 - 53
public function testGETProgrammersCollection()
{
... lines 56 - 64
$response = $this->client->get('/api/programmers');
... lines 66 - 68
$this->asserter()->assertResponsePropertyEquals($response, 'programmers[1].nickname', 'CowboyCoder');
}
... lines 71 - 72

And that's the super-power of the PropertyAccess component: it lets you walk down through the response data. This is really fun, give this a try:

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

We're still passing. If you change that to CowboyCoder2, we get that really clear failure message and the dumped JSON response right above it. We're dangerous. Change that test back so it passes.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony. The concepts of REST are still valid, but I recommend using API Platform in new Symfony apps.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.3.3",
        "symfony/symfony": "2.6.*", // v2.6.11
        "doctrine/orm": "~2.2,>=2.2.3,<2.5", // v2.4.7
        "doctrine/dbal": "<2.5", // v2.4.4
        "doctrine/doctrine-bundle": "~1.2", // v1.4.0
        "twig/extensions": "~1.0", // v1.2.0
        "symfony/assetic-bundle": "~2.3", // v2.6.1
        "symfony/swiftmailer-bundle": "~2.3", // v2.3.8
        "symfony/monolog-bundle": "~2.4", // v2.7.1
        "sensio/distribution-bundle": "~3.0,>=3.0.12", // v3.0.21
        "sensio/framework-extra-bundle": "~3.0,>=3.0.2", // v3.0.7
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.0
        "hautelook/alice-bundle": "0.2.*", // 0.2
        "jms/serializer-bundle": "0.13.*" // 0.13.0
    },
    "require-dev": {
        "sensio/generator-bundle": "~2.3", // v2.5.3
        "behat/behat": "~3.0", // v3.0.15
        "behat/mink-extension": "~2.0.1", // v2.0.1
        "behat/mink-goutte-driver": "~1.1.0", // v1.1.0
        "behat/mink-selenium2-driver": "~1.2.0", // v1.2.0
        "phpunit/phpunit": "~4.6.0" // 4.6.4
    }
}
userVoice