How to setup a test platform using Symfony2 and PHPUnit

Symfony2 and PHPUnit go together like mint and icecream, but out of the box Symfony2 does not provide a nice way to rebuild an applications database between tests, this is pretty crucial to building a effective test platform. Luckily some other chaps have already sat down, had a good ponder and come up with a nice solution to this issue.

Add the dependencies

First off you'll need to add the following dependcies to your project:

"doctrine/doctrine-fixtures-bundle": "2.2.*@dev",
"liip/functional-test-bundle": "1.0.*@dev"

Adding fixtures

If you haven't used fixtures before then let me explain their purpose, they provde a base set of data for insertion into a database, the point being that you have a set of data that you can guarantee will always be present for your tests, this is essential for testing an application.

The Doctrine Fixtures Bundle is the defacto Symfony2 standard for inserting fixtures and it is very straight forward to use. Within each of your bundles creat a /DataFixtures/Orm folder, you will need to create a class for each entity for which you wish to insert data, here is an example one:

<?php
/* src/Demo/UserBundle/DataFixtures/ORM/LoadUserData.php */

namespace Demo\UserBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Demo\UserBundle\Entity\User;

class LoadUserData extends AbstractFixture implements OrderedFixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $user = new User();
        $user->setName('Test User');
        $user->setUsername('test_user');
        $user->setEmail('test@user.com');

        $manager->persist($user);
        $manager->flush();
        
        $this->addReference('test-user', $user);
    }
    
    public function getOrder()
    {
        return 1;
    }
}

In this example I have created a fixture that simply adds a test user to the database, you can see that I use Doctrine2 to create the user rather than writing raw SQL. In my application I have entities that require a user to be added to them, the AbstractFixture class provides a method called addReference (use getReference to retrieve the object) that facilitates this. I have specified that I want this fixture to run first by using the getOrder function since this test sets up the 'test-user' reference that my other fixtures will need.

Here you can see a fixture that uses this reference:

<?php
/* src/Demo/PostBundle/DataFixtures/ORM/LoadPostData.php */

namespace Demo\PostBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Demo\PostBundle\Entity\Post;

class LoadPostData extends AbstractFixture implements OrderedFixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $post = new Post();
        $post->setUser($this->getReference('test-user'));
        $post->setTitle('Test Post');
        $post->setContent('Some test content');

        $manager->persist($post);
        $manager->flush();
    }
    
    public function getOrder()
    {
        return 2;
    }
}

You can see more examples of how to use the fixtures bundle here. To test your fixtures, fire up a console and run the following command from the root of your project:

php app/console doctrine:fixtures:load

This command will clear all data from the DB, so be cautious when running it. If all goes well you should see the data from your fixture classes in the DB.

Using fixtures with PHPUnit

Now that you have your test fixtures in place, the next step is to integrate them into PHPUnit so your tests get a nice fresh DB that has a dependable set of data within it, as mentioned before this is critical for your tests so you can guarantee that you start with a known set of data on which your tests can draw assertionas.

The LiipFunctionalTestBundle provides this much needed ability to clean the DB between tests, it also comes with some other useful bits for your testing, but we're just interested in getting the testrig actually setup for now.

First off we need to ensure that your application uses a separate test database when running tests, otherwise your main DB with all your precious data in it will get wiped whenever you want to test your changes, which to say the least would be a major flaw.

The easiest solution to this is to use a SQLite database, you can of course setup an actual DB within your platform of choice, but for now let's just go with the SQLite option.

Each Symfony2 project comes bundled with a test config file 'app/config_test.yml', within this file you can override any settings contained within your main config.yml, within this file add the following:

doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                driver:   pdo_sqlite
                path:     %kernel.cache_dir%/test.db

You might have to adjust this bit of config depending on your exact setup, but you can see the basic idea here...we are switching the driver over to SQLite and providing a path for the database file. One major advantage of this method is that SQLite is super quick, so your tests will be as well, however if you're code relies on complex or advanced SQL queries then you might see some errors or inconsistencies between your application in the real world and your application in test mode. If this occurs then simply create a test database within your database platform and use that instead.

The LiipFunctionalTestBundle comes with a replacement for Symfony2's WebTestCase class (here's the original class). We are interested in the loadFixtures function provided in this class, this function will drop and rebuild the test database, you have to pass an array of fixture classes into the function which will be loaded into the fresh test database. You want to run this function before each test so each one has it's own dependable environment in which to run, PHPUnit provides a setUp function that is executed before each test, this is the place to pop your loadFixtures call into:

/* src/Demo/PostBundle/Tests/Controller/PostControllerTest.php */

namespace Demo\PostBundle\Tests\Controller;

use Liip\FunctionalTestBundle\Test\WebTestCase;

class PostControllerTest extends WebTestCase
{
    public function setUp()
    {
        $classes = array(
            'Demo\PostBundle\DataFixtures\ORM\LoadPostData',
            'Demo\UserBundle\DataFixtures\ORM\LoadUserData',
        );
        $this->loadFixtures($classes);
    }

    /* Some tests */

}

It's highly likely that you'll want to load the same set of fixtures in each of your tests, so it's advisable to create your own WebTestCase that all of your tests can extend, create a TestBundle and pop your own WebTestCase in there with your custom setUp function.

Putting it all together

OK we now have a testrig setup so it's time to start the actual work, which is to write the tests for our application. I won't go into detail about that here, check Symfony 2's introduction to writing tests and also check out PHPUnit's documentation for a more detailed guide. The first place to start is to write tests for each of your applications controllers, then any supporting classes such as services, clients and whatnot. You can check my example project which includes everything discussed in this article.

Running PHPUnit

You'll obviously need to install PHPUnit, the easiest way to do this is via Pear like so:

pear config-set auto_discover 1
pear install pear.phpunit.de/PHPUnit

Once installed you can run PHPUnit by executing the following command:

phpunit -c app

The -c flag tells PHPUnit to load it's configuration from the app directory, Symfony2 comes with a config file in its app directory (called phpunit.xml) that instructs PHPUnit where to load it's tests from, this allows it to automatically find your tests in your bundles.

You can also specify a single bundle like so:

phpunit -c app src/Demo/PostBundle

PHPUnit can produce code coverage reports that tell you how much of your code has been covered by your tests, you just need to specify a directory to place the reports in like so:

phpunit -c app --coverage-html reports

That's it, you are ready to write a full set of tests for your application. If you need to test any services that rely on API's then you can use PHPUnit's mocker functions, I will write an article on how to do this soon.

Feel free to clone my example project for a guide on how to put all of this together.