Introducción

Laravel proporciona una variedad de herramientas útiles para hacer que sea más fácil probar tus aplicaciones que manejan base de datos. Primero, puedes usar el método (helper) assertDatabaseHas para comprobar que los datos existentes en la base de datos coinciden con un conjunto dado de criterios. Por ejemplo, si quisieras verificar que hay un registro en la tabla users con el valor email de [email protected], puedes hacer lo siguiente:

public function testDatabase()
{
    // Make call to application...

    $this->assertDatabaseHas('users', [
        'email' => '[email protected]',
    ]);
}

También podrías usar el método assertDatabaseMissing para comprobar que esos datos no existen en la base de datos.

El método assertDatabaseHas y otros métodos como éste son muy convenientes. Eres libre de usar cualquiera de los métodos de aserción integrados de PHPUnit para complementar tus pruebas funcionales (Feature tests).

Generando factories

Para crear un factory, usa el comando Artisan make:factory:

php artisan make:factory PostFactory

El nuevo factory será colocado en tu directorio database/factories.

La opción --model puede ser usada para indicar el nombre del modelo creado por el factory. Esta opción pre-completará el archivo de factory generado con el modelo dado:

php artisan make:factory PostFactory --model=Post

Reiniciando la base de datos después de cada prueba

Con frecuencia es útil reinicializar tu base de datos después de cada prueba de modo que los datos de una prueba previa no interfieran con las pruebas subsecuentes. El trait RefreshDatabase toma como enfoque más óptimo migrar tu base de datos de pruebas, dependiendo de si estás usando una base de datos en memoria o una base de datos tradicional. Usa el trait en tu clase de prueba y todo será manejado automáticamente sin que tengas que preocuparte por esto:

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
    * A basic functional test example.
    *
    * @return void
    */
    public function testBasicExample()
    {
        $response = $this->get('/');

        // ...
    }
}

Escribiendo factories

Al momento de probar, puedes necesitar insertar unos cuantos registros dentro de tu base de datos antes de ejecutar tu prueba. En lugar de especificar manualmente el valor de cada columna cuando crees estos datos de prueba, Laravel permite que definas un conjunto de atributos predeterminados para cada uno de tus modelos de Eloquent usando factories de modelos. Para empezar, demos un vistazo al archivo database/factories/UserFactory.php en tu aplicación. De forma predeterminada, este archivo contiene una definición de factory:

use Faker\Generator as Faker;
use Illuminate\Support\Str;

$factory->define(App\User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
        'remember_token' => Str::random(10),
    ];
});

Dentro del Closure, la cual sirve como la definición del factory, puedes devolver los valores de prueba predeterminados de todos los atributos del modelo. El Closure recibirá una instancia de la librería PHP Faker, la cual permitirá que generes convenientemente varios tipos de datos aleatorios para las pruebas.

También puedes crear archivos de factories adicionales para cada modelo para una mejor organización. Por ejemplo, podrías crear archivos UserFactory.php y CommentFactory.php dentro de tu directorio database/factories. Todos los archivos dentro del directorio factories serán cargados automáticamente por Laravel.

Puedes establecer la configuración regional de Faker agregando una opción faker_locale a tu archivo de configuración config/app.php.

Extendiendo Factories

Si has extendido un modelo, quizá querrás extender su factory también con el propósito de utilizar los atributos del factory del modelo hijo en las pruebas y los seeders. Para lograr esto, puedes ejecutar el método raw del constructor de factories para obtener el arreglo de atributos sin procesar de un factory dado:

$factory->define(App\Admin::class, function (Faker\Generator $faker) {
    return factory(App\User::class)->raw([
            // ...
    ]);
});

Estados de un factory

Los estados te permiten definir modificaciones discretas que pueden ser aplicadas a tus factories de modelos en cualquier combinación. Por ejemplo, tu modelo User podría tener un estado delinquent que modifique uno de sus valores de atributo predeterminados. Puedes definir tus transformaciones de estado usando el método state. Para estados simples, puedes pasar un arreglo de modificaciones de atributos:

$factory->state(App\User::class, 'delinquent', [
    'account_status' => 'delinquent',
]);

Si tu estado requiere cálculo o una instancia $faker, puedes usar una Closure para calcular las modificaciones de los atributos del estado:

$factory->state(App\User::class, 'address', function ($faker) {
    return [
        'address' => $faker->address,
    ];
});

LLamadas de retorno de un factory

Las llamadas de retorno (callbacks) de un Factory son registradas usando los métodos afterMaking y afterCreating y te permiten realizar tareas adicionales después de crear un modelo en memoria o persistente. Por ejemplo, puedes usar llamadas de retorno para relacionar modelos adicionales con el modelo creado:

$factory->afterMaking(App\User::class, function ($user, $faker) {
    // ...
});

$factory->afterCreating(App\User::class, function ($user, $faker) {
    $user->accounts()->save(factory(App\Account::class)->make());
});

También puedes definir llamadas de retorno para estados de un factory:

$factory->afterMakingState(App\User::class, 'delinquent', function ($user, $faker) {
    // ...
});

$factory->afterCreatingState(App\User::class, 'delinquent', function ($user, $faker) {
    // ...
});

Usando factories

Creando modelos

Una vez que hayas definido tus factories, puedes usar la función global factory en tus pruebas funcionales o en archivos seeder para generar instancias de un modelo. Así, vamos a dar un vistazo en unos pocos ejemplos de creación de modelos. Primero, usaremos el método make para crear modelos pero sin guardarlos en la base de datos:

public function testDatabase()
{
    $user = factory(App\User::class)->make();

    // Use model in tests...
}

También puedes crear una colección de muchos modelos o crear modelos de un tipo dado:

// Create three App\User instances...
$users = factory(App\User::class, 3)->make();

Aplicando estados

También puedes aplicar cualquiera de tus estados a los modelos. Si prefieres aplicar múltiples transformaciones de estado a los modelos, deberías especificar el nombre de cada estado que quisieras aplicar:

$users = factory(App\User::class, 5)->states('delinquent')->make();

$users = factory(App\User::class, 5)->states('premium', 'delinquent')->make();

Sobrescribiendo atributos

Si prefieres sobrescribir algunos de los valores predeterminados de tus modelos, puedes pasar un arreglo de valores al método make. Solamente los valores especificados serán reemplazados, mientras que el resto de los valores permanecerán con sus valores predeterminados como se especificó en el factory:

$user = factory(App\User::class)->make([
    'name' => 'Abigail',
]);

La protección de asignación masiva es deshabilitada automáticamente al momento de crear modelos usando factories.

Persistiendo modelos

El método create no solamente crea las instancias de un modelo sino que también los almacena en la base de datos usando el método save de Eloquent:

public function testDatabase()
{
    // Create a single App\User instance...
    $user = factory(App\User::class)->create();

    // Create three App\User instances...
    $users = factory(App\User::class, 3)->create();

    // Use model in tests...
}

Puedes sobrescribir atributos en el modelo al pasar un arreglo al método create:

$user = factory(App\User::class)->create([
    'name' => 'Abigail',
]);

Relaciones

En este ejemplo, adjuntaremos una relación para algunos modelos creados. Al momento de usar el método create para crear múltiples modelos, una instancia de colección de Eloquent es devuelta, permitiendo que uses cualquiera de las funciones convenientes proporcionadas por la colección, tales como each:

$users = factory(App\User::class, 3)
            ->create()
            ->each(function ($user) {
                $user->posts()->save(factory(App\Post::class)->make());
            });

Puedes usar el método createMany para crear varios modelos relacionados:

$user->posts()->createMany(
    factory(App\Post::class, 3)->make()->toArray()
);

Relaciones y closures de atributos

También puedes adjuntar relaciones a los modelos en tus definiciones del factory. Por ejemplo, si prefieres crear una nueva instancia User al momento de crear un Post, puedes hacer lo siguiente:

$factory->define(App\Post::class, function ($faker) {
    return [
        'title' => $faker->title,
        'content' => $faker->paragraph,
        'user_id' => factory(App\User::class),
    ];
});

Si la relación depende del factory que la define, debes proporcionar un callback que acepte el arreglo de atributos evaluados:

$factory->define(App\Post::class, function ($faker) {
    return [
        'title' => $faker->title,
        'content' => $faker->paragraph,
        'user_id' => factory(App\User::class),
        'user_type' => function (array $post) {
            return App\User::find($post['user_id'])->type;
        },
    ];
});

Usando Seeders

Si prefieres usar seeders de bases de datos para rellenar tu base de datos al momento de realizar una prueba funcional, puedes usar el método seed. Por defecto, el método seed retornará DatabaseSeeder, que debería ejecutar todos tus otros seeders. De forma alternativa, pasa un nombre de clase seeder específico al método seed:

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use OrderStatusesTableSeeder;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * Test creating a new order.
     *
     * @return void
     */
    public function testCreatingANewOrder()
    {
        // Run the DatabaseSeeder...
        $this->seed();

        // Run a single seeder...
        $this->seed(OrderStatusesTableSeeder::class);

        // ...
    }
}

Aserciones disponibles

Laravel proporciona varias aserciones de base de datos para tus pruebas funcionales PHPUnit:

Método Descripción
$this->assertDatabaseHas($table, array $data); Comprueba que una tabla en la base de datos contiene los datos dados.
$this->assertDatabaseMissing($table, array $data); Comprueba que una tabla en la base de datos no contiene los datos dados.
$this->assertDeleted($table, array $data); Comprueba que el registro dado ha sido eliminado.
$this->assertSoftDeleted($table, array $data); Comprueba que el registro dado ha sido borrado lógicamente.

Por conveniencia, puedes pasar un modelo a los helpers assertDeleted y assertSoftDeleted para comprobar que el registro fue eliminado o borrado lógicamente, respectivamente, de la base de datos en base a la clave primaria del modelo.

Por ejemplo, si estás usando un modelo inicializado con factory en tu prueba, puedes pasar este modelo a uno de estos helpers para probar si tu aplicación borró de forma apropiada el registro de la base de datos:

public function testDatabase()
{
    $user = factory(App\User::class)->create();

    // Make call to application...

    $this->assertDeleted($user);
}

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.

Lección anterior Laravel Dusk - Documentación de Laravel 6 Lección siguiente Mocking - Documentación de Laravel 6