Testez vos repository Doctrine dans une base de données SQLite (French)

2013-08-19

  symfony    doctrine    test    french 

Ma copine étant malade, j'en profite pour écrire un nouvel article technique sur comment tester vos repository Doctrine.

Les données retournées par les repository sont très souvent sensibles et découlent souvent de règles métiers.

C'est pourquoi il est important de tester unitairement vos repository Doctrine.

Configuration

Commençons par paramétrer Symfony afin d'utiliser une base SQLite pour l'environnement de test.

Editez votre fichier app/config/config_test.yml afin de spécifier à Doctrine d'utiliser le driver SQLite :


imports:
    - { resource: config_dev.yml }

doctrine:
    dbal:
        default_connection: default

        connections:
            default:
                driver:  pdo_sqlite
                user:    test
                path:    %kernel.root_dir%/sqlite.db.cache
                #memory: true
                charset: utf8
    orm:
        entity_managers:
            default:
                metadata_cache_driver: apc
                query_cache_driver:    apc
                result_cache_driver:   apc

Notez que le fichier de base de données sera ici stocké dans le répertoire app/sqlite.db.cache, lorsque celle-ci sera initialisée.

Maintenant, initialisons notre base de données de test :


$ php app/console doctrine:schema:update --env=test --force
Updating database schema...
Database schema updated successfully! "109" queries were executed

Votre base SQLite est maintenant prête. Passons au test unitaire.

Création du test unitaire du repository

Nous avons ici une classe nommée DoctrineTestCase permettant d'initialiser le repository et que nous allons faire étendre par tous nos fichiers de tests de repository :


namespace Eko\\MyBundle\\Tests\\Repository;

use Doctrine\\Common\\DataFixtures\\Purger\\ORMPurger;
use Doctrine\\Common\\DataFixtures\\Executor\\ORMExecutor;
use Doctrine\\Common\\DataFixtures\\Loader;

use Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase;

/**
 * Class DoctrineTestCase
 *
 * This is the base class to load doctrine fixtures using the symfony configuration
 */
class DoctrineTestCase extends WebTestCase
{
    /**
     * @var \\Symfony\\Component\\DependencyInjection\\Container
     */
    protected $container;

    /**
     * @var \\Doctrine\\ORM\\EntityManager
     */
    protected $em;

    /**
     * @var string
     */
    protected $environment = 'test';

    /**
     * @var bool
     */
    protected $debug = true;

    /**
     * @var string
     */
    protected $entityManagerServiceId = 'doctrine.orm.entity_manager';

    /**
     * Constructor
     *
     * @param string|null $name     Test name
     * @param array       $data     Test data
     * @param string      $dataName Data name
     */
    public function __construct($name = null, array $data = array(), $dataName = '')
    {
        parent::__construct($name, $data, $dataName);

        if (!static::$kernel) {
            static::$kernel = self::createKernel(array(
                'environment' => $this->environment,
                'debug'       => $this->debug
            ));
            static::$kernel->boot();
        }

        $this->container = static::$kernel->getContainer();
        $this->em = $this->getEntityManager();
    }

    /**
     * Executes fixtures
     *
     * @param \\Doctrine\\Common\\DataFixtures\\Loader $loader
     */
    protected function executeFixtures(Loader $loader)
    {
        $purger = new ORMPurger();
        $executor = new ORMExecutor($this->em, $purger);
        $executor->execute($loader->getFixtures());
    }

    /**
     * Load and execute fixtures from a directory
     *
     * @param string $directory
     */
    protected function loadFixturesFromDirectory($directory)
    {
        $loader = new Loader();
        $loader->loadFromDirectory($directory);
        $this->executeFixtures($loader);
    }

    /**
     * Returns the doctrine orm entity manager
     *
     * @return object
     */
    protected function getEntityManager()
    {
        return $this->container->get($this->entityManagerServiceId);
    }
}

Comme vous pouvez le remarquer, cette classe va également nous permettre de charger des fixtures Doctrine, que nous définirons par la suite. Utile, pour définir plusieurs cas de tests :)

Créeons maintenant notre fichier de test d'un repository imaginaire appelé CountryRepository d'une entité Country permettant de lister des pays.


namespace Eko\\MyBundle\\Tests\\Repository;

use Eko\\MyBundle\\Tests\\Repository\\DoctrineTestCase;

/**
 * Test the country repository
 */
class CountryRepositoryTest extends DoctrineTestCase
{
    /**
     * Set up repository test
     */
    public function setUp()
    {
        $this->loadFixturesFromDirectory(__DIR__ . '/DataFixtures');
    }

    /**
     * Test finding all countries ordered
     */
    public function testFindAllOrdered()
    {
        $countries = $this->getRepository()->findAllOrdered();

        $this->assertCount(3, $countries, 'Should return 3 countries');

        $this->assertEquals('EN', $countries[0]->getCountryCode());
        $this->assertEquals('FR', $countries[1]->getCountryCode());
        $this->assertEquals('IT', $countries[2]->getCountryCode());
    }

    /**
     * Returns repository
     *
     * @return \\Eko\\MyBundle\\Repository\\CountryRepository
     */
    protected function getRepository()
    {
        return $this->em->getRepository('\\Eko\\MyBundle\\Entity\\Country');
    }
}

Nous allons donc ici tester une méthode findAllOrdered() permettant de retourner les pays par ordre alphabétique.

Remarquez également que nous chargeons les fixtures depuis un répertoire parent nommé DataFixtures.

Ajoutons des fixtures !

Nous créeons donc le fichier LoadCountryData.php suivant :


namespace Eko\\MyBundle\\Tests\\Repository\\DataFixtures;

use Doctrine\\Common\\DataFixtures\\AbstractFixture;
use Doctrine\\Common\\Persistence\\ObjectManager;

use Eko\\MyBundle\\Entity\\Country;

/**
 * Loads countries data
 */
class LoadCountryData extends AbstractFixture
{
    /**
     * Load fixtures
     *
     * @param \\Doctrine\\Common\\Persistence\\ObjectManager $manager
     */
    public function load(ObjectManager $manager)
    {
        $manager->clear();
        gc_collect_cycles(); // Could be useful if you have a lot of fixtures

        // France
        $country = new Country();
        $country->setCountryCode('FR');
        $country->setName('France');
        $this->addReference('test-country-fr', $country);

        $manager->persist($country);

        // Italy
        $country = new Country();
        $country->setCountryCode('IT');
        $country->setName('Italy');
        $this->addReference('test-country-it', $country);

        $manager->persist($country);

        // England
        $country = new Country();
        $country->setCountryCode('EN');
        $country->setName('England');
        $this->addReference('test-country-en', $country);

        $manager->persist($country);

        $manager->flush();
    }
}

Rien de spécial par ici, nous chargeons 3 entrées dans notre table country (France, Italy, England).

Notre base de données SQLite contient donc nos fixtures puisque celles-ci sont chargées à l'initialisation de notre test unitaire par la méthode loadFixturesFromDirectory().

Notre test unitaire est donc prêt à être exécuté :


$ phpunit -c app src/Eko/MyBundle/Tests/Repository/CountryRepositoryTest.php
PHPUnit 3.7.19 by Sebastian Bergmann.

Configuration read from /vagrant/www/perso/blog/app/phpunit.xml.dist

.

Time: 1 second, Memory: 24.50Mb

OK (1 test, 4 assertions)

J'espère que ce cas d'utilisation pourra vous aider à mieux tester vos repository Doctrine.

Private: J'espère aussi qu'Eugénie va déjà un peu mieux désormais !  

→ Plus d'informations sur la documentation de Symfony : http://symfony.com/fr/doc/current/cookbook/testing/doctrine.html

Comments