Symfony PHPUnit testing database data

Today I had to test some complex algorithm logic we’re writing for a client app. The algorithm I want to test interacts with the DDBB several times through query builders and repositories, so it’s necessary to test this database access, as we need to know the sql queries give the right data also.

It’s the first time I set up a test environment (I worked with Unit Testing before, but never set up the env from scratch). So after reading a bit of doc from the internet, I set up the unit test environment, then wrote some tests that interacted with a test database and test fixtures.

I’ve put together a bit of guide for anyone wanting to get started on this.

Step 1 – Setting a separate database for testing.

Add these lines on your config_test.yml file.


#config_test.yml

...

# Doctrine Configuration
doctrine:
    dbal:
        driver:   pdo_mysql
        host:     "%test_database_host%"
        port:     "%test_database_port%"
        dbname:   "%test_database_name%"
        user:     "%test_database_user%"
        password: "%test_database_password%"
        # Workaround for DBAL 2.5 auto-detect -> with server_version allows to doctrine:database:create
        server_version: 5.6 # your database server version here

This will point to the new test database.

Then add the data in parameters.yml (don’t forget parameters.yml.dist for future installs)


# This file is auto-generated during the composer install
parameters:

    ...

    test_database_host: localhost
    test_database_port: null
    test_database_name: test_db_name
    test_database_user: test_db_user
    test_database_password: test_db_pw

Step 2 – Creating a TestCase that we will extend when ddbb fixtures are needed.

This is useful for reusing code, instead of writing it on every file. It also makes all the fixture tests more consistent.

class DataFixtureTestCase extends WebTestCase
{
    /** @var  Application $application */
    protected static $application;

    /** @var  Client $client */
    protected $client;
    
    /** @var  ContainerInterface $container */
    protected $container;

    /** @var  EntityManager $entityManager */
    protected $entityManager;

    /**
     * {@inheritDoc}
     */
    public function setUp()
    {
        self::runCommand('doctrine:database:drop --force');
        self::runCommand('doctrine:database:create');
        self::runCommand('doctrine:schema:create');
        self::runCommand('doctrine:fixtures:load --append --no-interaction --fixtures=tests/AppBundle/DataFixtures/ORM');

        $this->client = static::createClient();
        $this->container = $this->client->getContainer();
        $this->entityManager = $this->container->get('doctrine.orm.entity_manager');

        parent::setUp();
    }

    protected static function runCommand($command)
    {
        $command = sprintf('%s --quiet', $command);

        return self::getApplication()->run(new StringInput($command));
    }

    protected static function getApplication()
    {
        if (null === self::$application) {
            $client = static::createClient();

            self::$application = new Application($client->getKernel());
            self::$application->setAutoExit(false);
        }

        return self::$application;
    }
    
    /**
     * {@inheritDoc}
     */
    protected function tearDown()
    {
        self::runCommand('doctrine:database:drop --force');

        parent::tearDown();

        $this->entityManager->close();
        $this->entityManager = null; // avoid memory leaks
    }
}

Most of the code is taken from StackOverflow.

This will create the database and install the fixtures on every test that extends this TestCase. This is important as you want a consistent database with the same data every time!

It’s also important to pass the parameter

--fixtures=tests/AppBundle/DataFixtures/ORM'

This way you can set up all your test fixtures separate from your app fixtures, which will stay clean.

Step 3 – Creating the unit, functional and integration tests

After creating your test fixtures, now you can test your services logic easily by extending the TestCase we just created, like so.

class FooTest extends DataFixtureTestCase
{
    protected $fooService;

    /**
     * {@inheritDoc}
     */
    public function setUp()
    {
        parent::setUp();
        $this->fooService = $this->container->get('app.service.foo');
    }

    public function testFooTrue()
    {

        ... 
        //custom logic
        ... 

        $this->assertEquals(true, $value);
    }

  
}

And that’s all you need! Happy testing! 🙂

PS: This is a very easy way to get started. For more complex testing environment settings, you can try LiipFunctionalTestBundle


Also published on Medium.

2 thoughts on “Symfony PHPUnit testing database data”

  1. Hello,
    Thanks for sharing this.
    I encountered the same situation some time ago on a project. What we did was almost the same however, instead of having a database that is destroyed/created on the setUp of a test, we created a Phing task that did it. We also put in that task the loading of the database fixtures, and the test suite execution. The developer XP was really good because the test suite ran very quickly and I believe this one of the key point for developers to write unit tests.

    1. Hi there! That sounds very interesting! The solution I provided was in fact a first step for creating some tests with database data, but when reaching a higher number of tests it becomes slow to run them all. I’ll definitely have a look at your idea – sounds like a much faster and better experience for the dev 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *