Symfony. Mejorando tus tests con DoctrineFixturesBundle
Crea datos de prueba para tus tests con el bundle DoctrineFixturesBundle
English version: https://medium.com/@ger86/symfony-improving-your-tests-with-doctrinefixturesbundle-1a37b704ac05
Una de las cosas más tediosas a las que nos tenemos que enfrentar cuando escribimos los tests de nuestra aplicación es la generación de los datos de prueba.
Por ejemplo, supongamos que queremos escribir los tests relacionados con el blog de la web que estamos desarrollando. Para realizar determinados tests probablemente necesitemos que en base de datos haya ya entidades creadas de modo que podamos navegar por ellas o probar las diferentes acciones asociadas a las mismas.
La opción más simple que tenemos para esto es crear directamente esos datos desde nuestra clase Test
algo que a la larga sin embargo no es mantenible pues terminaríamos con una gran cantidad de código repetido (en el momento en que tengamos varias entidades dependientes las unas de las otras) y que “ensuciaría” la propia clase.
Es aquí donde aparece DoctrineFixturesBundle, el cual nos va a simplificar muchísimo todo este proceso. ¡Vamos a verlo!
Instalación
Como comentaba en la introducción del artículo, DoctrineFixturesBundle permite la inserción de datos de prueba en nuestra base de datos de cara a realizar tests u otro tipo de acciones. Además, es compatible con cualquier base de datos con la que lo sea Doctrine (MySQL, SQLite…)
Para instalar este bundle en nuestro proyecto (suponiendo que estáis empleando la versión 4 de Symfony) recurriremos a composer
:
composer require --dev orm-fixtures
Cuya recipe
realizará el resto de acciones necesarias para dejar configurado el bundle aunque nunca está de más asegurarnos de que se ha añadido la siguiente línea a nuestro archivo bundles.php
:
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true]
Escribiendo nuestra primera Fixture
Una vez instalado el bundle, en la carpeta src
se habrá añadido la carpeta DataFixtures
que es el lugar donde crearemos las clases para nuestras Fixtures.
Para crear una clase Fixture
lo único que necesitamos es crearla dentro de la carpeta DataFixtures
y extender de la clase Fixture
que proporciona el bundle.
Por ejemplo, supongamos que como dije al principio necesitamos escribir los tests de un blog. Lo que haremos será generar la clase PostFixtures
:

Como podéis ver el código es bastante sencillo. Dentro del método load
perteneciente a la clase Doctrine\Bundle\FixturesBundle\Fixture
lo que haremos será crear 20 entidades de tipo Post
y ejecutar el método flush
del EntityManager
como haríamos normalmente.
Ahora, para cargar estos datos en base de datos ejecutaremos desde línea de comandos la siguiente instrucción:
bin/console doctrine:fixtures:load
y ¡listo! Ya tenemos 20 artículos de prueba para realizar nuestros tests.
¡👁️! Al ejecutar ese comando se “purga” la base de datos, por lo que si queréis tan sólo añadir dichos datos deberéis añadir la opción “append”.
Si os estáis preguntando lo que hace el comando la respuesta es muy sencilla. Gracias a la opción autoconfigure
, Symfony es capaz de etiquetar las clases declaradas como servicios (que por defecto son todas menos aquellas que se encuentren en las carpetas Entity) en función de la interfaz que implementan. En nuestro caso, puesto que estamos heredando de Fixture
, esta clase abstracta implementa la interfaz ORMFixtureInterface
, por lo que todas las clases que hereden de ella serán catalogadas como Fixtures
y por tanto cargadas por medio del comando anterior.
Cómo crear Fixtures dependientes
Antes de pasar a ver cómo podemos emplear las Fixtures en nuestros tests, me gustaría hablar de la forma en que podemos crear Fixtures que dependan de otras.
Volviendo a nuestro ejemplo podría ser que cada artículo perteneciese a una categoría, por lo que también habría que crear unas cuantas categorías en nuestros datos de prueba.
Pese a que podríamos generar estas categorías dentro de la propia clase PostFixtures
, la solución más adecuada pasa por implementar la interfaz DependentFixturesInterface
de modo que podamos relacionar unas fixtures con otras. Vamos a ver cómo hacerlo.
Lo primero que haremos será crear nuestra clase PostCategoryFixtures.php
:

Puesto que PostCategoryFixtures
vamos a usarla posteriormente como dependencia de PostCategory
el código es algo distinto al de la Fixture anterior:
- En la línea 21 estoy empleando
addReference
para decir al bundle que añada una referencia del objeto de Doctrine$category
para posteriormente poderlo buscar. Este método recibe dos argumentos, la clave para guardar la referencia al objeto y el objeto en sí mismo. - En la línea 11 estoy creando un método estático de cosecha propia de cara a generar referencias para los objetos que cree la Fixture en función del índice del bucle.
Hecho esto, modificaremos nuestro archivo PostFixtures
para añadir lo siguiente:

Las modificaciones son las siguientes:
- En las líneas 9 y 11 esto importando la interfaz
DependentFixtureInterface
e implementándola. - En la línea 16 estoy empleando el método
getReference
de la clase abstractaFixture
para recuperar una referencia a un objeto en Doctrine previamente almacenada. Para ello necesito la clave del objeto, la cual la genero con el método estáticogetReferenceKey
de la clasePostCategoryFixtures
al que paso un índice entre 0 y 4 (puesto que sólo he creado 5 categorías). - En la línea 30 declaro las dependencias de esta Fixture, de modo que cuando sea cargada por medio del comando
doctrine:fixtures:load
también se ejecuten las Fixtures indicadas como dependencias.
Gracias a esto, ya podremos generar artículos de prueba junto con sus categorías pertinentes.
Cómo acceder a servicios dentro de una Fixture
Otra de las situaciones que se nos pueden plantear cuando estamos generando Fixtures es tener que emplear algún servicio, por ejemplo, si estamos creando usuarios de prueba igual necesitamos acceder al servicio UserPasswordEncoderInterface
para codificar sus contraseñas.
Esto sin embargo es muy fácil de resolver gracias a que todas nuestras Fixtures son (salvo que hayáis especificado lo contrario en el archivo services.yaml
) servicios, de modo que podemos inyectar dependencias de la forma habitual por medio del constructor:
public function __construct(UserPasswordEncoderInterface $encoder)
{
$this->encoder = $encoder;
}
Aún así, y en casos extremos tenemos dos soluciones alternativas.
La primera es acceder a la propiedad $this->container
de la propia Fixture
pero que tiene la limitación de no podernos devolver aquellos servicios que hayamos declarado como privados.
La segunda es implementar la interfaz Symfony\Component\DependencyInjection\ContainerAwareInterface
y emplear dentro de la fixture el trait
Symfony\Component\DependencyInjection\ContainerAwareTrait
de modo que también tengamos acceso a $this->container
.
Integrando las fixtures con PHPUnit
Una vez que ya estamos familiarizados con la forma en que podemos crear Fixtures llega el momento de integrar el bundle con PHPUnit porque, de momento, para añadir nuestras Fixtures a base de datos es necesario ejecutar el comando:
bin/console doctrine:fixtures:load
Esto provoca que cada vez que corramos nuestros tests tengamos que asegurarnos de que se han cargado las Fixtures, algo que es muy fácil que pasemos por alto. Lo ideal sería que cada test se encargara de cargar en base de datos las Fixtures con las que va a trabajar para lo cual existen dos alternativas.
LiipTestFixturesBundle
El bundle LiipTestFixturesBundle estaba integrado originalmente en el bundle LiipFunctionalTestBundle el cual añade una serie de características muy interesantes a la suite de PHPUnit como la ejecución de tests en paralelo o la creación de clientes autenticados para los tests end-to-end. Sin embargo, a partir de la versión 3.0 la funcionalidad de cargar fixtures fue separada a un bundle aparte.
Su instalación la haremos por medio de composer:
composer require --dev liip/test-fixtures-bundle:^1.0.0
Lo cual nos permitirá emplear añadir el trait Liip\TestFixturesBundle\Test\FixturesTrait
a nuestras clases de test. De este modo, en cada método que defina un test podremos emplear el método loadFixtures
para especificar qué Fixtures queremos que se carguen cuando se vaya a ejecutar ese test:
use Liip\TestFixturesBundle\Test\FixturesTrait;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class MyControllerTest extends WebTestCase
{
use FixturesTrait;
public function testIndex()
{
// add all your fixtures classes that implement
// Doctrine\Common\DataFixtures\FixtureInterface
$this->loadFixtures(array(
'App\DataFixtures\PostFixtures',
'App\DataFixtures\ProjectFixtures'
));
...
Además este bundle presenta alguna que otra funcionalidad interesante como la posibilidad de excluir algunas tablas de ser purgadas cuando se cargan las fixtures o la opción de añadir las fixtures a las tablas en vez de que éstas sean purgadas previamente.
Solución de andar por casa
Por otra parte, si preferís no recurrir a un bundle de tercero siempre podéis crear en vuestro proyecto la siguiente clase:

Esta clase abstracta provee del método addFixture
a las clases que la extiendan para añadir las Fixtures que queramos ejecutar y del método executeFixtures
para ejecutar las Fixtures así añadidas. Por ejemplo:
class DeletePostControllerTest extends AuthenticatedClientWebTestCase {protected function setUp() { $kernel = self::bootKernel(); $this->addFixture(new PostFixtures()); $this->executeFixtures();}
El único “pero” de esta forma es que debemos instanciar las clases de las Fixtures antes de pasárselas al método addFixture
( en nuestro ejemplo new PostFixtures()
) lo cual provoca que si necesitamos inyectar dependencias seamos nosotros quienes tengamos que hacerlo.
En resumen…
Como veis, DoctrineFixturesBundle nos facilita enormemente escribir tests, centralizando además en un único sitio la creación de datos de prueba. Además, podemos emplear este bundle para rellenar la base de datos de modo que podamos enseñar cómo quedaría la aplicación una vez que empezase a haber datos en ella, algo que resulta muy útil en las presentaciones al cliente.
Espero que os haya gustado el artículo. ¡Nos vemos en los siguientes!
¿Quieres recibir más artículos como este?
Si te ha gustado este artículo te animo a que te suscribas a la newsletter que envío cada domingo con publicaciones similares a esta y más contenido recomendado: 👇👇👇