Tester ses Formulaires Symfony2 avec le type de champs Entity

Dans cet article nous allons vous montrer comment mettre en place un TestCase symfony2 vous permettant de tester des formulaires qui font appel au type de champs 'entity'. L'objectif est multiple, on veut pouvoir tester réellement l'acquisition d'entités par le formulaire et aussi pouvoir avoir accès à la chaîne de validation du framework.

Les choses à savoir

Tout d'abord, pour ceux qui ne le savent pas, le type de champs 'entity' ne fait pas partie du Composant Form de Symfony car il est spécifique à l'intégration de Doctrine. Il se trouve donc dans le Bridge Doctrine intégré dans la distribution.

Le type de champs 'entity' est apporté par l'extension de fomulaire DoctrineORMExtension qui se trouve dans le bridge. Cette extension nécessite l'injection d'une classe spécifique ManagerRegistry permettant de récupérer les instances des différents gestionnaires d'entités (EntityManager). Il va nous falloir créer un bouchon de test (Mockup) du ManagerRegistry pour qu'il nous renvoi toujours une instance contrôlée du gestionnaire d'entité.

Les entités que l'on va utiliser pour faire nos tests devront être de vraies instances, car il va falloir les persister dans l'entity manager.

Doctrine est livré avec une classe DoctrineTestHelper permettant de simplifier la création d'un EntityManager de test. Il faut installer l'extension PDO SQLite pour que cette procédure fonctionne.

Il faut à un moment ou un autre expliquer à notre instance d'EntityManager quelles sont les entités qu'il gère, donc, les entités que l'on va vouloir utiliser dans les champs 'entity' lors de nos tests.

Si on veut que le type de champs entity fonctionne sur nos entités, il faudra les persister dans notre base de données. Ce qu'on propose c'est de formaliser tout ça dans une classe spécifique FormTypeTestCase que l'on pourra réutiliser dans notre projet.

L'objet FormTypeTestCase

<?php
namespace Acme\Bundle\DemoBundle\Tests\PHPUnit;

/* imports */
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Form\Forms;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\Extension\Validator\Type\FormTypeValidatorExtension;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper;
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;

/**
 * Test case used to test form types with entity field
 *
 * @author Nicolas MACHEREY <nicolas.macherey@gmail.com>
 */
abstract class FormTypeTestCase extends TypeTestCase
{
    /**
     * @var EntityManager
     */
    private $em;

    /**
     * @bar ManagerRegistry
     */
    private $emRegistry;

    /**
     * {@inheritdoc}
     */
    protected function setUp()
    {
        $this->em = DoctrineTestHelper::createTestEntityManager();
        $this->emRegistry = $this->createRegistryMock('default', $this->em);

        parent::setUp();

        $this->createSchema();

        $validator = $this->getMock('\Symfony\Component\Validator\ValidatorInterface');
        $validator->expects($this->any())->method('validate')->will($this->returnValue(new ConstraintViolationList()));

        $this->factory = Forms::createFormFactoryBuilder()
            ->addExtensions($this->getExtensions())
            ->addTypeExtension(
                new FormTypeValidatorExtension(
                    $validator
                )
            )
            ->addTypeGuesser(
                $this->getMockBuilder(
                    'Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser'
                )
                    ->disableOriginalConstructor()
                    ->getMock()
            )
            ->getFormFactory();

        $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
        $this->builder = new FormBuilder(null, null, $this->dispatcher, $this->factory);

    }

    /**
     * Create the schema that will be used for testing
     */
    protected function createSchema()
    {
        $schemaTool = new SchemaTool($this->em);
        $classes = [];

        foreach ($this->getEntities() as $entityClass) {
            $classes[]= $this->em->getClassMetadata($entityClass);
        }

        try {
            $schemaTool->dropSchema($classes);
        } catch (\Exception $e) {
        }

        try {
            $schemaTool->createSchema($classes);
        } catch (\Exception $e) {
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function getExtensions()
    {
        return array_merge(parent::getExtensions(), array(
            new DoctrineOrmExtension($this->emRegistry),
        ));
    }

    /**
     * Return the entities to map with the entity manager
     *
     * @return array
     */
    protected function getEntities()
    {
        return array();
    }

    /**
     * Create a mock of entity manager registry
     *
     * @param string        $name
     * @param EntityManager $em
     *
     * @return \Doctrine\Common\Persistence\ManagerRegistry
     */
    protected function createRegistryMock($name, $em)
    {
        $registry = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry');
        $registry->expects($this->any())
                 ->method('getManager')
                 ->with($this->equalTo($name))
                 ->will($this->returnValue($em));

        $registry->expects($this->any())
                 ->method('getManagerForClass')
                 ->will($this->returnValue($em));

        return $registry;
    }
}

La classe de test

Voilà il ne vous reste plus qu'à faire votre classe de test et de la faire étendre de notre FormTestCase.

    /**
     * Unit Test Suite for MyFormType
     */
    class MyFormTypeTest extends FormTestCase
    {
        /**
         * {@inheritdoc}
         */
        protected function getEntities()
        {
            // Register entities used in 'entity' fields here
            return array_merge(parent::getEntities(), array(
                'Acme\Bundle\Entity\Foo',
                'Acme\Bundle\Entity\Bar',
            ));
        }

        public function testForm()
        {
            $foos = [];
            // Create real foo instances here

            // ...
            $this->persist($foos);

            $bars = [];
            // Create real bar instances here

            // ...
            $this->persist($bars);

            // Create submition data
            $formData = array(
                'foo' => $foos[0]->getId(), // Register proper Ids here
                'bar' => $bars[0]->getId(), // Register proper Ids here
            );

            $type = new MyFormType();
            $form = $this->factory->create($type);
            $form->submit($formData);
            $this->assertTrue($form->isSynchronized());

            // Perform custom assertions there
            $this->assertEquals($foos[0], $form->getData()->getFoo());
            $this->assertEquals($bars[0], $form->getData()->getBar());
            // ...

            // Ensure the view has all required variables
            $view = $form->createView();
            $children = $view->children;
            foreach (array_keys($formData) as $key) {
                $this->assertArrayHasKey($key, $children);
            }
        }
     }

Voilà nous n'irons pas plus loin le sujet n'étant pas de vous expliquer comment tester correctement vos formulaires personnalisés, mais plus de vous permettre de tester réellement avec des entités dans une base de données.

J'espère que ça vous sera utile !

Comments

comments powered by Disqus