If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeIn 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.
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 | |
} |
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.
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.
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.
"Houston: no signs of life"
Start the conversation!
// 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
}
}