Symfony Unit Testing – With a Database

When you are writing tests, you will want to test code that interacts with a database. You can do a functional test, or you can test your repository implementations. You will want to make sure that your queries actually work.

But offcourse, you don’t want to use your production database for testing. You need to have a database which does not affect production. You will want to reset this database, so that every time you run a test, it starts from the same state. And even more important, it should be blazing fast.

There is no need to test with MySQL. You could use the faster SQLite. Remember. You are not testing Doctrine. You are testing your application that uses Doctrine. This means we can expect that doctrine queries behave the same in SQLite and MySQL.

So we have 3 problems to solve if we want to create these tests. We need a solution to load in test data. We should have a blazing fast test database.  And we need a mechanic to reset the database back to its initial state.

Creating fixtures

We can solve our first problem by using data fixtures. We should require the doctrine fixtures bundle.

composer require --dev doctrine/doctrine-fixtures-bundle

Fixtures are used to load in a fake set of data.  For example; we can create a fixture that loads in some fake products. We can then load this fixture every time we want to test a query. This means every time we run the test, the database will start from the that state.

Here is an example of a data fixture for loading in 5 simple products.

<?php
namespace App\DataFixtures;


use App\Entity\Product;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;

class ProductsFixture extends Fixture
{

    public function load(ObjectManager $manager)
    {
        for ($i = 1; $i <= 5; $i++) {
            $product = Product::create('Product ' . $i);
            $manager->persist($product);
        }

        $manager->flush();
    }
}

Now we have created a fixture which we can load in our tests.

Setting up SQLite for testing

We can solve the second problem by setting up a SQLite database for testing.

Why SQLite? Well it’s fast and easy to setup. SQLite is an in-process library which is completely server less. In essence, its only a file.

First we should create a new file test.db3 in our var directory. This will hold the test database.

Then we create a new doctrine.yaml configuration file.

# config/packages/test/doctrine.yaml
doctrine:
    dbal:
        driver: 'pdo_sqlite'
        url: 'sqlite:///%kernel.project_dir%/var/test.db3'

Next we change in phpunit.xml.dist the following line:

<env name="DATABASE_URL" value="sqlite:///%kernel.project_dir%/var/test.db3"/>

With these things setup. Symfony and PHPunit will know about this test database.

Liip Functional Test Bundle

To solve or last problem we can use a handy bundle called Liip Functional Test Bundle. This bundle provides base classes for functional tests. It assists us in setting up test database and make it easy to load in fixtures for our tests. Its exactly what we need to solve our second problem.

If you are using Symfony 4 you will need to install the 2.0 version of this bundle.

composer require --dev liip/functional-test-bundle:~2.0@alpha

Fixture Aware TestCase

But before we can create our Repository Test, we should create a FixtureAwareTest class that we can re-use for testing Repositories. This make sure we need even less code.

<?php

namespace App\Tests;

use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

/**
 * Class FixtureAwareTestCase
 * @package App\Tests
 */
abstract class FixtureAwareTestCase extends KernelTestCase
{
    /**
     * @var ORMExecutor
     */
    private $fixtureExecutor;

    /**
     * @var ContainerAwareLoader
     */
    private $fixtureLoader;

    protected function setUp()
    {
        static::bootKernel();
    }

    /**
     * Adds a new fixture to be loaded.
     */
    protected function addFixture(FixtureInterface $fixture): void
    {
        $this->getFixtureLoader()->addFixture($fixture);
    }

    /**
     * Executes all the fixtures that have been loaded so far.
     */
    protected function executeFixtures(): void
    {
        $this->getFixtureExecutor()->execute($this->getFixtureLoader()->getFixtures());
    }

    /**
     * Get the class responsible for loading the data fixtures.
     * And this will also load in the ORM Purger which purges the database before loading in the data fixtures
     */
    private function getFixtureExecutor(): ORMExecutor
    {
        if (!$this->fixtureExecutor) {
            /** @var \Doctrine\ORM\EntityManager $entityManager */
            $entityManager = static::$kernel->getContainer()->get('doctrine')->getManager();
            $this->fixtureExecutor = new ORMExecutor($entityManager, new ORMPurger($entityManager));
        }

        return $this->fixtureExecutor;
    }

    /**
     * Get the Doctrine data fixtures loader
     */
    private function getFixtureLoader(): ContainerAwareLoader
    {
        if (!$this->fixtureLoader) {
            $this->fixtureLoader = new ContainerAwareLoader(static::$kernel->getContainer());
        }

        return $this->fixtureLoader;
    }
}

Creating the Repository Test

Now we can create our ProductRepositoryTest by extending from our FixtureAwareTestCase.

<?php
namespace App\Tests\Repository;


use App\DataFixtures\ProductsFixture;
use App\Repository\ProductRepository;
use App\Tests\FixtureAwareTestCase;
use Doctrine\ORM\EntityManager;

class ProductRepositoryTest extends FixtureAwareTestCase
{
    /**
     * @var EntityManager
     */
    private $entityManager;

    /**
     * @var ProductRepository
     */
    private $productRepository;

    protected function setUp()
    {
        parent::setUp();

        // Define the fixtures to be loaded. We will request to load in our 5 products
        $this->addFixture(new ProductsFixture());
        // This will purge the test database and load in all the requested fixtures
        $this->executeFixtures();

        // Boot up the Symfony Kernel
        $kernel = static::bootKernel();
        // Lets get the entityManager from the container
        $this->entityManager = $kernel->getContainer()->get('doctrine')->getManager();
        $this->productRepository = new ProductRepository($this->entityManager);
    }

    public function testFind(): void
    {
        $product = $this->productRepository->find(5);

        // We know that the 5th product is named Product 5
        $this->assertEquals('Product 5', $product->getName());
    }
}

As you can see, we now have an easy way to test Repositories with an actual database. The Liip Functional Test Bundle together with the abstract FixtureAwareTestCase makes it easy to create these kinds of tests.