Symfony PHPUnit tests using Doctrine

You can use Symfony features to easily create our test case. It automatically keeps your database schema up-to-date, creates fixtures and removes data after a test.

The basic use of PHPUnit with Symfony is well documented in the Official Symfony documentation, but what about tests using a relational database with Doctrine? It doesn’t work “out of the box” in Symfony. So we have to write it ourselves. Here you can read about different approaches for maintaining a database with tests. In this article, I’ll show you how to implement the “Update when needed” process with fixtures.

Keeping database up-to-date

Nobody wants to think about maintaining the test database. Just create a schema once and set up the credentials in the configuration file(config/test/config.yml). Then don’t worry about updating it - it will be done automatically. There is great functionality to do it hassle-free - using Doctrine’s SchemaTool::updateSchema(). The main downside is that it can take some time, depending on the size of database schema (from 1 second for a small/medium schema to several seconds on a big schema!). I created a separate class because it’s needed to extend the KernelTestCase to get the application’s kernel.

class GenerateSchema extends KernelTestCase
{
    public function generate()
    {
        $kernel = static::createKernel();
        $kernel->boot();
        $entityManager = $kernel->getContainer()->get('doctrine.orm.entity_manager');
        $metadata = $entityManager->getMetadataFactory()->getAllMetadata();

        $tool = new SchemaTool($entityManager);
        $tool->updateSchema($metadata);
    }
}

You can use the above class in function DbTestCase::setUpBeforeClass(), but it would execute before every test class, which is a complete runtime time waste. It’s better to execute it once. It doesn’t matter if we run all tests, a specific test suite or a single test. It will be always executed once. To do that you will need to create a custom bootstrap.php to add these functionalities, e.g. tests.bootstrap.php.

<?php
require __DIR__ . '/bootstrap.php.cache';
$schema = new \Tests\GenerateSchema();
$schema->generate();

Then set the new bootstrap in the phpunit configuration file (default: app/phpunit.xml.dist)

<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         bootstrap="tests.bootstrap.php"
>
...
</phpunit>

Remember to execute phpunit using this configuration, e.g. phpunit -c app/phpunit.xml.dist tests/


Fixtures

To have the possibility if adding specific fixtures for a test you can use Symfony’s fixture functionality. There are two methods you can use: DbTestCase::addFixture() and DbTestCase::execute() which can be used by chaining, e.g.

$this->addFixture(new UsersFixture())->executeFixtures();

I extended WebTestCase because I would use it to test HTTP responses. If you don’t need it, just extend KernelTestCase. Then you will need to extend this test case in your every test class. Also, I exposed EntityManager for use in tests, to get the necessary repositories from entity class.

abstract class DbTestCase extends WebTestCase
{
    /**
     * @var Loader
     */
    private $loader;

    /**
     * @var EntityManager
     */
    public static $em;

    protected function setUp()
    {
        parent::setUp();
        self::bootKernel();
        self::$em = self::$kernel->getContainer()->get('doctrine.orm.entity_manager');
    }

    /**
     * Adds a new fixture to be loaded.
     *
     * @param FixtureInterface $fixture
     * @return $this
     */
    protected function addFixture(FixtureInterface $fixture)
    {
        if (!$this->loader) {
            $this->loader = new Loader();
        }
        $this->loader->addFixture($fixture);
        return $this;
    }

    /**
     * Executes all the fixtures that have been loaded so far.
     */
    protected function executeFixtures()
    {
        $purger = new ORMPurger();
        $executor = new ORMExecutor(self::$em, $purger);
        $executor->execute($this->loader->getFixtures(), true);  //append fixtures intead of cleaning
    }
}

Cleaning the database

If you have fixtures for every table you set ORMExecutor to remove data from a database for tables with fixtures. However, it’s not a bulletproof solution. Imagine if a new table is created without a fixture. Then data in this table will be not cleaned after each test execution. There is a fast and efficient solution - wrapping every single test into a database transaction. To do it we can use this library for Symfony (doctrine-test-bundle)

Remember to clean the whole schema used for tests before using it again.