PHP
Article

Quick Tip: Testing Symfony Apps with a Disposable Database

By Andrew Carter

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.

  • http://www.janbask.net/ Janbask – Online IT Training

    This is quite helpful

  • http://darsadow.com/ Dariusz Sadowski

    Only one problem when you’re using some features which are available in your production database, let’s say MySQL, and they are not part of sqlite. Then you need to hack one side or another. Also from my experience tests against one database engine can give false positives and program will fail with production database.
    Also testing should be done without any database connection using proper mocking. But it’s different story :)

    This is good if you have just simple CRUD app.

    • Jerzy

      “Also testing should be done without any database connection using proper mocking.”
      Yes, if we are talking about unit testing which of course should be biggest part of our test suite.
      No, if we are talking about integration tests which are also important.

      With unit tests you can test if you build query correctly, with integration you can test if you build correct query – both sides are important here.

      • http://darsadow.com/ Dariusz Sadowski

        But why would you test queries using different RDBMS?

        • Jerzy

          About that I agree – I was only referring to your part of comment about testing db – not about using different RDBms

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.