Considerando Laravel “Model Factory Stories”

Photo by Samuel Zeller on Unsplash

Hace algunos días escuchaba un podcast muy interesante donde se hablaba del tema de Migrations y Model Factories, allí surgió esta propuesta de “Factory Stories” que me pareció realmente interesante y decidí invertir unas cuantas horas en desarrollar esta idea y finalmente convertirla en un paquete.

Mi intención con esta publicación es hablar un poco sobre los fundamentos detrás de esta propuesta y dar algunos ejemplos de uso, esperando por supuesto recibir comentarios de la comunidad de Laravel que puedan contribuir al desarrollo de una mejor solución :)

¿Qué son model factories?

Los model factories permiten definir una serie de valores por defecto para los atributos de cada uno de los modelos de tu aplicación. Comúnmente se utilizan en entorno de pruebas o desarrollo para generar fácilmente registros dentro de una base de datos.

When testing, you may need to insert a few records into your database before executing your test. Instead of manually specifying the value of each column when you create this test data, Laravel allows you to define a default set of attributes for each of your Eloquent models using model factories. To get started, take a look at the database/factories/ModelFactory.php file in your application. Out of the box, this file contains one factory definition
https://laravel.com/docs/5.4/database-testing#writing-factories

¿ Cómo se define un Model Factory ?

Los model factories se escriben en el archivo database/factories/ModelFactory.php de la siguiente manera:

$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'password' => bcrypt('secret'),
'remember_token' => str_random(10),
'role' => 'guest'
    ]
});

Utilizando el componente Faker se pueden generar tipos de datos de forma aleatoria, como email, nombres, direcciones, etc.

Usando Model Factories en el entorno de pruebas

Gracias a esta herramienta podemos crear tantos registros como sean necesarios para nuestras pruebas.

public function testBasicTest()
{
$admin = factory(App\User::class)->create([
'role' => 'admin'
]);
    $users = factory(App\User::class, 10)->create();
}

Por supuesto podemos crear registros mucho más complejos, por ejemplo: “articulo con taxonomies creado por un usuario de tipo author con estatus publish

public function testBasicTest()
{
$user = factory(App\User::class)->create([
'role' => 'author'
]);
    $article = factory(App\Article::class)->create([
'status' => 'publish',
'user_id' => $user->id
]);
    $tags = factory(App\Tag::class, 3)->create([
'status' => 'publish'
]);
    $categories = factory(App\Tag::class, 3)->create([
'status' => 'publish'
]);
    $article->tags()->attach($tags->pluck('id'));
$article->categories()->attach($categories->pluck('id'));
}

Cuando queremos usar este tipo de “articulo” en varias pruebas (funciones) diferentes, podemos extraer el código a un método reusable:

public function publishedArticleWithTaxonomies()
{
$user = factory(App\User::class)->create([
'role' => 'author'
]);
    $article = factory(App\Article::class)->create([
'status' => 'publish',
'user_id' => $user->id
]);
    $tags = factory(App\Tag::class, 3)->create([
'status' => 'publish'
]);
    $categories = factory(App\Tag::class, 3)->create([
'status' => 'publish'
]);
    $article->tags()->attach($tags->pluck('id'));
$article->categories()->attach($categories->pluck('id'));

return $article->fresh();
}

Y si queremos reusar este código en varias clases de pruebas podemos extraerlo a un nuevo trait o a una nueva clase

El problema

A medida que la aplicación va creciendo, necesitaremos crear registros con ciertas particularidades un poco más complejos, relacionando varios modelos entre si, entonces en lugar de crear traits o clases con demasiado código, podemos extraer cada “historia” a su propia clase.

Mi propuesta a este tema es el uso de Model Factory Story

Cuando pensamos en “articulo con taxonomies creado por un usuario de tipo author con estatus publish” podemos ver un tipo de “historia” allí (No se me ha ocurrido un mejor nombre para ello).

La idea es crear clases dónde podamos definir este tipo de “historias” que puedan ser usadas desde cualquier “test” creando tantos registros del mismo tipo como sea necesario.

¿Cómo funcionan?

Una vez instalado el paquete se puede ejecutar desde la terminal el siguiente comando para crear un nuevo “Factory Story”

$ php artisan make:factory-story PublishedArticleWithTaxonomies

Esto va a generar un nuevo archivo dentro del directorio database/

<?php

use App\Models\User;
use FactoryStories\FactoryStory;

class TestStory extends FactoryStory
{
public function build($params = [])
{
$user = factory(App\User::class)->create([
'role' => 'author'
]);
        $article = factory(App\Article::class)->create([
'status' => 'publish',
'user_id' => $user->id
]);
        $tags = factory(App\Tag::class, 3)->create([
'status' => 'publish'
]);
        $categories = factory(App\Tag::class, 3)->create([
'status' => 'publish'
]);
        $article->tags()->attach($tags->pluck('id'));
$article->categories()->attach($categories->pluck('id'));

return $article->fresh();
}

}

¿Cuál es la ventaja?

De esta manera podemos tener un poco más de organización al momento de escribir este tipo de estructuras “complejas” que permiten crear datos de prueba.

La principal ventaja es que pueden ser reutilizados dentro de cualquier método de pruebas que estemos escribiendo de la siguiente manera:

public function testBasicTest()
{

$articles = (new PublishedArticleWithTaxonomies)
->times(5)->create();
}

Nota Importante (Actualización)

Este paquete ya se encuentra disponible en su versión 1.0 en packagist y GitHub

Si utilizas Laravel te invito a probar esta herramienta, todos los comentarios son bienvenidos :)

Gracias por haber llegado hasta aquí!!

Antes de cerrar esta página, te pido que compartas esta publicación con tus colegas programadores y otros miembros de la comunidad de Laravel.

Photo by Matt Jones on Unsplash
Like what you read? Give Jeff a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.