This tutorial has a new version, check it out!

Tests with the Container

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

Using a random nickname in a test is weird: we should be explicit about our input and output. Just set it to ObjectOrienter. Now it's easy to make our asserts more specific, like for the Location header using assertEquals, which should be /api/programmers/ObjectOrienter. And now use the method getHeader():

... lines 1 - 7
public function testPOST()
{
$data = array(
'nickname' => 'ObjectOrienter',
'avatarNumber' => 5,
'tagLine' => 'a test dev!'
);
... lines 15 - 22
$this->assertEquals('/api/programmers/ObjectOrienter', $response->getHeader('Location'));
... lines 24 - 26
}
... lines 28 - 29

And at the bottom, assertArrayHasKey is good, but we really want to say assertEquals() to really check that the nickname key coming back is set to ObjectOrienter:

... lines 1 - 24
$this->assertArrayHasKey('nickname', $finishedData);
$this->assertEquals('ObjectOrienter', $finishedData['nickname']);
... lines 27 - 29

This test makes me happier. But does it pass? Run it!

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

Sawheet! All green. Untilllllll you try it again:

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

Now it explodes - 500 status code and we can't even see the error. But I know it's happening because nickname is unique in the database, and now we've got the nerve to try to create a second ObjectOrienter.

Booting the Container

Ok, we've gotta take control of the stuff in our database - like by clearing everything out before each test.

If we had the EntityManager object, we could use it to help get that done. So, let's boot the framework right inside ApiTestCase. But not to make any requests, just so we can get the container and use our services.

Symfony has a helpful way to do this - it's a base class called KernelTestCase:

... lines 1 - 6
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class ApiTestCase extends KernelTestCase
{
... lines 11 - 55
}

Inside setupBeforeClass(), say self::bootKernel():

... lines 1 - 17
public static function setUpBeforeClass()
{
... lines 20 - 26
self::bootKernel();
}
... lines 29 - 56

The kernel is the heart of Symfony, and booting it basically just makes the service container available.

Add the tearDown() method... and do nothing. What!? This is important. I'm adding a comment about why - I'll explain in a second:

... lines 1 - 36
/**
* Clean up Kernel usage in this test.
*/
protected function tearDown()
{
// purposefully not calling parent class, which shuts down the kernel
}
... lines 44 - 56

But first, create a private function getService() with an $id argument. Woops - make that protected - the whole point of this method is to let our test classes fetch services from the container. To do that, return self::$kernel->getContainer()->get($id):

... lines 1 - 50
protected function getService($id)
{
return self::$kernel->getContainer()
->get($id);
}

The whole point of that KernelTestCase base class is to set and boot that static $kernel property which has the container on it. Now normally, the base class actually shuts down the kernel in tearDown(). What I'm doing - on purpose - is booting the kernel and creating the container just once per my whole test suite.

That'll make things faster, though in theory it could cause issues or even slow things down eventually. You can experiment by shutting down your kernel in tearDown() and booting it in setup() if you want. Or even just clearing the EntityManager to avoid a lot of entities getting stuck inside of it after a bunch of tests.

Clearing Data

Because we have the container, we have the EntityManager. And that also means we have an easy way to clear data. Create a new private function called purgeDatabase(). Because we have the Doctrine DataFixtures library installed, we can use a great class called ORMPurger. Pass it the EntityManager - so $this->getService('doctrine')->getManager(). To clear things out, say $purger->purge():

... lines 1 - 4
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
... lines 6 - 8
class ApiTestCase extends KernelTestCase
{
... lines 11 - 44
private function purgeDatabase()
{
$purger = new ORMPurger($this->getService('doctrine')->getManager());
$purger->purge();
}
... lines 50 - 55
}

Now we just need to call this before every test - so calling this in setup() is the perfect spot - $this->purgeDatabase():

... lines 1 - 29
protected function setUp()
{
$this->client = self::$staticClient;
$this->purgeDatabase();
}
... lines 36 - 56

This should clear the ObjectOrienter out of the database and hopefully get things passing. Try the test!

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

Drumroll! Oh no - still a 500 error. And we still can't see the error. Time to take our debugging tools up a level.

Leave a comment!

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
    }
}