Test Fixtures and the PropertyAccess Component
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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeHowdy big error! Now that I can see you, I can fix you! Remember, back in ProgrammerController
, we're always assuming there's a weaverryan
user in the database:
// ... lines 1 - 19 | |
public function newAction(Request $request) | |
{ | |
// ... lines 22 - 25 | |
$form->submit($data); | |
$programmer->setUser($this->findUserByUsername('weaverryan')); | |
$em = $this->getDoctrine()->getManager(); | |
$em->persist($programmer); | |
$em->flush(); | |
// ... lines 33 - 42 | |
} | |
// ... lines 44 - 98 |
We'll fix this later with some proper authentication, but for now, when we run our tests, we need to make sure that user is cozy and snug in the database.
Creating a test User
Create a new protected function
called createUser()
with a required username
argument and one for plainPassword
. Make that one optional: in this case, we don't care what the user's password will be:
I'll paste in some code for this: it's pretty easy stuff. I'll trigger autocomplete on the User
class to get PhpStorm to add that use
statement for me. This creates the User
and gives it the required data. The getService()
function we created lets us fetch the password encoder out so we can use it, what a wonderfully trained function:
// ... lines 1 - 212 | |
protected function createUser($username, $plainPassword = 'foo') | |
{ | |
$user = new User(); | |
$user->setUsername($username); | |
$user->setEmail($username.'@foo.com'); | |
$password = $this->getService('security.password_encoder') | |
->encodePassword($user, $plainPassword); | |
$user->setPassword($password); | |
// ... lines 221 - 226 | |
} | |
// ... lines 228 - 259 |
Let's save this! Since we'll need the EntityManager
a lot in this class, let's add a protected function getEntityManager()
. Use getService()
with doctrine.orm.entity_manager
. And since I love autocomplete, give this PHPDoc:
// ... lines 1 - 226 | |
/** | |
* @return EntityManager | |
*/ | |
protected function getEntityManager() | |
{ | |
return $this->getService('doctrine.orm.entity_manager'); | |
} | |
// ... lines 234 - 235 |
Now $this->getEntityManager()->persist()
and $this->getEntityManager()->flush()
. And just in case whoever calls this needs the User
, let's return it.
// ... lines 1 - 210 | |
protected function createUser($username, $plainPassword = 'foo') | |
{ | |
// ... lines 213 - 219 | |
$em = $this->getEntityManager(); | |
$em->persist($user); | |
$em->flush(); | |
return $user; | |
} | |
// ... lines 226 - 235 |
We could just go to the top of testPOST
and call this there. But really, our entire system is kind of dependent on this user. So to truly fix this, let's put it in setup()
. Don't forget to call parent::setup()
- we've got some awesome code there. Then, $this->createUser('weaverryan')
:
// ... lines 1 - 5 | |
class ProgrammerControllerTest extends ApiTestCase | |
{ | |
protected function setUp() | |
{ | |
parent::setUp(); | |
$this->createUser('weaverryan'); | |
} | |
// ... lines 14 - 34 | |
} |
I'd say we've earned a greener test - let's try it!
phpunit -c app src/AppBundle/Tests/Controller/Api/ProgrammerControllerTest.php
Yay!
Testing GET one Programmer
Now, let's test the GET programmer endpoint:
// ... lines 1 - 5 | |
class ProgrammerControllerTest extends ApiTestCase | |
{ | |
// ... lines 8 - 35 | |
public function testGETProgrammer() | |
{ | |
// ... lines 38 - 51 | |
} | |
} |
Hmm, so we have another data problem: before we make a request to fetch a single programmer, we need to make sure there's one in the database.
To do that, call out to an imaginary function createProgrammer()
that we'll write in a second. This will let us pass in an array of whatever fields we want to set on that Programmer
:
// ... lines 1 - 35 | |
public function testGETProgrammer() | |
{ | |
$this->createProgrammer(array( | |
'nickname' => 'UnitTester', | |
'avatarNumber' => 3, | |
)); | |
// ... lines 42 - 51 | |
} | |
// ... lines 53 - 54 |
The Programmer
class has a few other fields and the idea is that if we don't pass something here, createProgrammer()
will invent some clever default for us.
Let's get to work in ApiTestCase
: protected function createProgrammer()
with an array of $data
as the argument. And as promised, our first job is to use array_merge()
to pass in some default values. One is the powerLevel
- it's required - and if it's not set, give it a random value from 0 to 10. Next, create the Programmer
:
// ... lines 1 - 228 | |
protected function createProgrammer(array $data) | |
{ | |
$data = array_merge(array( | |
'powerLevel' => rand(0, 10), | |
// ... lines 233 - 235 | |
), $data); | |
// ... lines 237 - 238 | |
$programmer = new Programmer(); | |
// ... lines 240 - 247 | |
} | |
// ... lines 249 - 258 |
Ok, maybe you're expecting me to iterate over the data, put the string set
before each property name, and call that method. But no! There's a better way.
Getting down with PropertyAccess
Create an $accessor
variable that's set to ProperyAccess::createPropertyAccessor()
. Hello Symfony's PropertyAccess component! Now iterate over data. And instead of the "set" idea, call $accessor->setValue()
, pass in $programmer
, passing $key
- which is the property name - and pass in the $value
we want to set:
// ... lines 1 - 228 | |
protected function createProgrammer(array $data) | |
{ | |
// ... lines 231 - 237 | |
$accessor = PropertyAccess::createPropertyAccessor(); | |
$programmer = new Programmer(); | |
foreach ($data as $key => $value) { | |
$accessor->setValue($programmer, $key, $value); | |
} | |
// ... lines 243 - 247 | |
} | |
// ... lines 249 - 258 |
The PropertyAccess
component is what works behind the scenes with Symfony's Form component. So, it's great at calling getters and setters, but it also has some really cool superpowers that we'll need soon.
The Programmer
has all the data it needs, except for this $user
relationship property. To set that, we can just add user
to the defaults and query for one. I'll paste in a few lines here: I already setup our UserRepository
to have a findAny()
method on it:
// ... lines 1 - 228 | |
protected function createProgrammer(array $data) | |
{ | |
$data = array_merge(array( | |
'powerLevel' => rand(0, 10), | |
'user' => $this->getEntityManager() | |
->getRepository('AppBundle:User') | |
->findAny() | |
), $data); | |
// ... lines 237 - 247 | |
} | |
// ... lines 249 - 258 |
And finally, the easy stuff! Persist and flush that Programmer
. And return it too for good measure:
// ... lines 1 - 228 | |
protected function createProgrammer(array $data) | |
{ | |
// ... lines 231 - 242 | |
$this->getEntityManager()->persist($programmer); | |
$this->getEntityManager()->flush(); | |
return $programmer; | |
} | |
// ... lines 249 - 258 |
Finishing the GET Test
Phew! With that work done, finishing the test is easy. Make a GET
request to /api/programmers/UnitTester
. And as always, we want to start by asserting the status code:
// ... lines 1 - 35 | |
public function testGETProgrammer() | |
{ | |
$this->createProgrammer(array( | |
'nickname' => 'UnitTester', | |
'avatarNumber' => 3, | |
)); | |
$response = $this->client->get('/api/programmers/UnitTester'); | |
$this->assertEquals(200, $response->getStatusCode()); | |
// ... lines 45 - 51 | |
} | |
// ... lines 53 - 54 |
I want to assert that we get the properties we expect. If you look in ProgrammerController
, we're serializing 4 properties: nickname
, avatarNumber
, powerLevel
and tagLine
. To avoid humiliation let's assert that those actually exist.
I'll use an assertEquals()
and put those property names as the first argument in a moment. For the second argument - the actual value - we can use array_keys()
on the json decoded response body - which I'll cleverly call $data
. Guzzle can decode the JSON for us if we call $response->json()
. This gives us the decoded JSON and array_keys
gives us the field names in it. Back in the first argument to assertEquals()
, we'll fill in the fields: nickname
, avatarNumber
, powerLevel
and tagLine
- even if it's empty:
// ... lines 1 - 35 | |
public function testGETProgrammer() | |
{ | |
// ... lines 38 - 42 | |
$response = $this->client->get('/api/programmers/UnitTester'); | |
$this->assertEquals(200, $response->getStatusCode()); | |
$data = $response->json(); | |
$this->assertEquals(array( | |
'nickname', | |
'avatarNumber', | |
'powerLevel', | |
'tagLine' | |
), array_keys($data)); | |
} | |
// ... lines 53 - 54 |
Ok, time to test-drive this:
phpunit -c app src/AppBundle/Tests/Controller/Api/ProgrammerControllerTest.php
Great success! Now let's zero in and make our assertions a whole lot more ...assertive :)
There was 1 failure:
1) AppBundle\Tests\Controller\Api\ProgrammerControllerTest::testGETProgrammer
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
Array (
- 0 => 'nickname'
- 1 => 'avatarNumber'
- 2 => 'powerLevel'
- 3 => 'tagLine'
+ 0 => 'id'
+ 1 => 'nickname'
+ 2 => 'avatar_number'
+ 3 => 'power_level'
+ 4 => 'user'
)
C:\Users\rakib\Site\symfony2-rest\src\AppBundle\Tests\Controller\Api\ProgrammerControllerTest.php:59
avatar_number become avatarNumber ...
in Windows