A la hora de realizar los tests funcionales de una aplicación web, debemos de trabajar con un conjunto de datos controlados que permitan interactuar con ellos y probar los distintos casos de uso, como puede ser editar un elemento, visualizar un elemento o ejecutar una acción sobre este. Generalmente estos tests funcionales simulan peticiones HTTP
contra nuestra aplicación.
Este conjunto de datos que utilizamos se le conoce comúnmente como Fixture. La palabra fixture viene del mundo de la electrónica ya que son una serie de pinzas que se usan de base para probar componentes electrónicos.
En el mundo de Symfony y muy unido al ORM Doctrine se suele utilizar una librería recomendada por la propia documentación del framework llamada DoctrineFixturesBundle. Esta librería nos ayuda a construir estos fixtures y lanzarlos en nuestro sistema de una manera controlada para ser usados en nuestros tests o como datos iniciales para que la aplicación pueda empezar a funcionar.
Llevo utilizando este sistemas de fixtures desde hace tiempo, pero hoy concretamente me he enfrentado a un problema a la hora de relanzar los fixtures. Cuando configuras en el método setup()
el lanzamiento de fixtures, estos se lanzan y se hidrata la base de datos y tras pasar el test se hace un truncate de las tablas, para volver a rellenarlas. Esto implica que siempre tienes el mismo conjunto de datos controlados en cada test.
El problema que me he encontrado es cuando automáticamente se ejecuta el truncado de las tablas contra el mysql, la bbdd se encontraba con ciertas foreign keys que no podía borrar, comenzando a fallar el resto de tests. El error encontrado era algo similar al siguiente:
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (my_db.Ent, CONSTRAINT FK_FE5D1D1E727ACA70 FOREIGN KEY (parent_id) REFERENCES Ent (id))
Tras indagar por StackOverFlow la mejor manera de automatizar el truncado es ignorando las foreign keys con la siguiente solución, facil, sencilla y para toda la familia 🙂
<?php namespace Acme\MyBundle\Tests\Controller; use Liip\FunctionalTestBundle\Test\WebTestCase; class MyWebTestCase extends WebTestCase { protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null) { $this->getContainer()->get('doctrine')->getManager()->getConnection()->query(sprintf('SET FOREIGN_KEY_CHECKS=0')); $result = parent::loadFixtures($classNames, $omName, $registryName, $purgeMode); $this->getContainer()->get('doctrine')->getManager()->getConnection()->query(sprintf('SET FOREIGN_KEY_CHECKS=1')); return $result; } }
Como se puede observar se ejecutan dos sentencias SQL antes y después de lanzar los fixtures en cuestión. Estas sentencias son SET FOREIGN_KEY_CHECKS=0
para deshabilitar el chequeo de las claves foráneas y SET FOREIGN_KEY_CHECKS=1
para volver habilitarlo tras haber pasado el test.
De esta manera los tests han vuelto a funcionar y mi vida ha sido mas fácil 🙂 Espero que disfruten el consejo Symfony del día.