Quick Tip: Testing Symfony Apps with a Disposable Database

Andrew Carter
Share

Testing code that interacts with a database can be a massive pain. Some developers mock database abstractions, and thus do not test the actual query. Others create a test database for the development environment, but this can also be a pain when it comes to continuous integration and maintaining the state of this database.

Symfony logo

In-memory databases are an alternative to these options. As they exist only in the memory of the application, they are truly disposable and great for testing. Thankfully, these are very easy to set up with Symfony applications that use Doctrine. Try reading our guide to functional testing with Symfony after this to learn about testing the end-to-end behaviour of an application.

The Symfony Environment Configuration

One of the most powerful features of the Symfony framework is the ability to create different environments with their own unique configuration. This feature can be overlooked by Symfony developers, especially when it comes to the lesser known test environment that is investigated here.

The Symfony guide to mastering and creating new environments explains how the framework handles the configuration for different environments and shows some useful examples. The configuration file that needs to be edited to set up disposable test databases is app/config/config_test.php. When the application is accessed in the test suite, the kernel will be loaded using the test environment and this configuration file will be processed.

In-Memory Databases with Doctrine

SQLite3 supports in-memory databases which are great for testing. Using these, an application can be tested by actually sending SQL queries to a functioning database, removing the need to tediously mock repository classes with predefined behaviour. The database will be fresh at the start of the test and cleanly destroyed at the end.

To override the default doctrine connection configuration, the lines below need to be added to the test environment configuration file. If there are multiple doctrine connections configured in the application, it may need to be adapted slightly to match.

# app/config/config_test.yml

doctrine:
    dbal:
        driver:  pdo_sqlite
        memory:  true
        charset: UTF8

Using the Database in Test Classes

When using this shiny new in-memory database in test classes, the schema must first be constructed. This means creating the tables from the entities and loading any fixtures that are required for the test suite.

The class below can be used as a database primer that does most of this work. It has the same effect as forcefully running the doctrine schema update console command.

<?php

namespace Tests\AppBundle;

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Component\HttpKernel\KernelInterface;

class DatabasePrimer
{
    public static function prime(KernelInterface $kernel)
    {
        // Make sure we are in the test environment
        if ('test' !== $kernel->getEnvironment()) {
            throw new \LogicException('Primer must be executed in the test environment');
        }

        // Get the entity manager from the service container
        $entityManager = $kernel->getContainer()->get('doctrine.orm.entity_manager');

        // Run the schema update tool using our entity metadata
        $metadatas = $entityManager->getMetadataFactory()->getAllMetadata();
        $schemaTool = new SchemaTool($entityManager);
        $schemaTool->updateSchema($metadatas);

        // If you are using the Doctrine Fixtures Bundle you could load these here
    }
}

If the entity manager is required to test a class, the primer must be applied:

<?php

namespace Tests\AppBundle;

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Tests\AppBundle\DatabasePrimer;

class FooTest extends KernelTestCase
{
    public function setUp()
    {
        self::bootKernel();

        DatabasePrimer::prime(self::$kernel);
    }

    public function testFoo()
    {
        $fooService = self::$kernel->getContainer()->get('app.foo_service');

        // ...
    }
}

In the example above the container is used to get the service that is being tested. If this service depends on the entity manager, it will be constructed with the same entity manager that is primed in the setUp method. If more control is required, maybe to mock another dependency, the entity manager can always be retrieved from the container and used to manually instantiate the class that needs testing.

It may also be a good idea to use the Doctrine Fixtures Bundle to populate the database with test data, but this will depend on your use case.