Laravel

Laravel

Tutorial Laravel: Pruebas de integración en Laravel

Aprende pruebas de integración en Laravel para evaluar la interacción entre componentes como controladores y modelos, mejorando la calidad de tu sistema.

Aprende Laravel GRATIS y certifícate

Conceptos básicos de pruebas de integración

Las pruebas de integración en Laravel permiten verificar cómo interactúan diferentes componentes del sistema. A diferencia de las pruebas unitarias, que se centran en partes aisladas del código, las pruebas de integración evalúan el funcionamiento conjunto de múltiples elementos como controladores, modelos y vistas.

Laravel proporciona una clase base de pruebas, TestCase, que facilita la creación de pruebas de integración. Al extender esta clase, podemos aprovechar métodos que simulan solicitudes HTTP, interactuar con la base de datos y utilizar herramientas propias del framework para las pruebas.

Para simular solicitudes y verificar respuestas, disponemos de métodos como get, post, put y delete. Estos métodos permiten enviar solicitudes HTTP simuladas a la aplicación y comprobar que las respuestas son las esperadas. Por ejemplo:

public function testVerPaginaInicio()
{
    $response = $this->get('/');

    $response->assertStatus(200);
    $response->assertSee('Bienvenido a Laravel');
}

En este ejemplo, se verifica que la ruta '/' devuelve un estado HTTP 200 y que la página contiene el texto esperado.

Las pruebas de integración suelen requerir una interacción con la base de datos. Para ello, es recomendable utilizar una base de datos en memoria o una base de datos de prueba. Laravel facilita este proceso mediante el uso del trait RefreshDatabase, que reinicia la base de datos antes de cada prueba:

use Illuminate\Foundation\Testing\RefreshDatabase;

class UsuarioTest extends TestCase
{
    use RefreshDatabase;

    public function testCrearUsuario()
    {
        $this->post('/usuarios', [
            'nombre' => 'Juan',
            'email' => 'juan@example.com',
            'password' => 'secreto123',
        ]);

        $this->assertDatabaseHas('users', [
            'email' => 'juan@example.com',
        ]);
    }
}

Este ejemplo muestra cómo verificar que, tras realizar una solicitud POST, el usuario ha sido guardado en la base de datos.

Cuando las pruebas involucran autenticación o middleware, podemos simular un usuario autenticado utilizando el método actingAs:

public function testAccesoAdministracion()
{
    $usuario = User::factory()->create([
        'role' => 'admin',
    ]);

    $response = $this->actingAs($usuario)->get('/admin');

    $response->assertStatus(200);
}

Aquí, se simula que un usuario con rol de administrador accede a una ruta protegida.

Para garantizar pruebas fiables, es importante utilizar datos de prueba coherentes. Las factories permiten crear instancias de modelos con datos predefinidos, facilitando la generación de registros necesarios durante las pruebas.

Además, es posible probar aspectos como la validación de formularios, comprobando que la aplicación responde adecuadamente ante datos incorrectos:

public function testValidacionFormulario()
{
    $response = $this->post('/registro', [
        'email' => 'no-es-un-email',
        'password' => '123',
    ]);

    $response->assertSessionHasErrors(['email', 'password']);
}

En este caso, se verifica que el sistema muestra errores de validación cuando los datos proporcionados no cumplen las reglas definidas.

Las pruebas de integración también permiten comprobar el funcionamiento de eventos, colas y otros componentes del sistema. Al integrar diferentes partes de la aplicación en un entorno de prueba controlado, podemos detectar y corregir errores que no serían evidentes en pruebas unitarias aisladas.

Las herramientas que ofrece Laravel simplifican la creación y ejecución de pruebas de integración, fomentando el desarrollo de aplicaciones más sólidas y mantenibles.

Uso de TestCase y base de datos

En las pruebas de integración de Laravel, la clase TestCase es la base para configurar el entorno de pruebas y proporcionar herramientas que facilitan el testing de la aplicación. Al extender de TestCase, se heredan métodos y propiedades que permiten simular solicitudes HTTP, interactuar con la base de datos y configurar el ambiente.

Para empezar, es fundamental configurar correctamente la base de datos para pruebas. En el archivo phpunit.xml, se pueden establecer variables de entorno específicas para el entorno de testing:

<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>

Con esta configuración, Laravel utilizará una base de datos SQLite en memoria, lo que agiliza la ejecución de las pruebas al no depender de una base de datos física.

Dentro de las clases de prueba, es común utilizar el trait RefreshDatabase, que se encarga de refrescar la base de datos antes de cada test:

use Illuminate\Foundation\Testing\RefreshDatabase;

class UsuarioTest extends TestCase
{
    use RefreshDatabase;

    // Métodos de prueba aquí
}

El trait RefreshDatabase garantiza que la base de datos se encuentre en un estado limpio antes de cada prueba, aplicando las migraciones necesarias. Esto es crucial para evitar que los datos de una prueba afecten a las siguientes.

Para poblar la base de datos con datos necesarios para las pruebas, se pueden utilizar factories. Las factories permiten crear instancias de modelos con atributos predefinidos o aleatorios:

public function testCreacionDeUsuarios()
{
    $usuarios = Usuario::factory()->count(10)->create();

    $this->assertCount(10, Usuario::all());
}

En este ejemplo, se crean diez usuarios en la base de datos de pruebas y se verifica que realmente existan. Las factories son una herramienta poderosa para generar datos realistas de forma sencilla.

Además de las factories, los seeders pueden ser útiles para insertar datos específicos necesarios para las pruebas:

public function setUp(): void
{
    parent::setUp();
    $this->seed(RolSeeder::class);
}

Con el método seed, se ejecuta un seeder que, por ejemplo, puede insertar roles predefinidos en la base de datos. Esto es útil cuando las pruebas dependen de una cierta configuración inicial.

Para interactuar con la aplicación durante las pruebas, la clase TestCase proporciona métodos que simulan solicitudes HTTP. Por ejemplo, para enviar una solicitud GET:

public function testVerPerfilUsuario()
{
    $usuario = Usuario::factory()->create();

    $response = $this->actingAs($usuario)->get('/perfil');

    $response->assertStatus(200);
    $response->assertSee($usuario->nombre);
}

Aquí, se simula que un usuario autenticado accede a la ruta /perfil, y se comprueba que la respuesta es correcta y que contiene el nombre del usuario. El método actingAs es útil para simular un usuario autenticado durante las pruebas.

Si es necesario enviar datos en las solicitudes, se pueden utilizar métodos como post, put o delete, pasando los datos como un array asociativo:

public function testActualizacionDePerfil()
{
    $usuario = Usuario::factory()->create();

    $datosActualizados = [
        'nombre' => 'Nuevo Nombre',
        'email' => 'nuevo_email@example.com',
    ];

    $response = $this->actingAs($usuario)->put('/perfil', $datosActualizados);

    $response->assertRedirect('/perfil');
    $this->assertDatabaseHas('usuarios', ['email' => 'nuevo_email@example.com']);
}

En este caso, se envía una solicitud PUT para actualizar el perfil del usuario y se verifica que los cambios se hayan reflejado en la base de datos. La aserción assertDatabaseHas es útil para comprobar directamente el estado de la base de datos tras una acción.

Para pruebas que requieran datos más complejos, es posible utilizar el método json para enviar solicitudes con contenido JSON:

public function testCreacionDeProducto()
{
    $datosProducto = [
        'nombre' => 'Producto de Prueba',
        'precio' => 99.99,
        'descripcion' => 'Descripción del producto de prueba',
    ];

    $response = $this->postJson('/api/productos', $datosProducto);

    $response
        ->assertStatus(201)
        ->assertJsonPath('nombre', 'Producto de Prueba');

    $this->assertDatabaseHas('productos', ['nombre' => 'Producto de Prueba']);
}

Este ejemplo muestra cómo enviar una solicitud POST en formato JSON y verificar tanto la respuesta como el estado de la base de datos.

Para situaciones en las que se necesita simular o mockear componentes o servicios externos, TestCase ofrece métodos que facilitan esta tarea:

public function testNotificacionEnviada()
{
    Notification::fake();

    $usuario = Usuario::factory()->create();

    // Acción que debería enviar una notificación
    $this->post('/enviar-notificacion', ['usuario_id' => $usuario->id]);

    // Verificar que se envió la notificación
    Notification::assertSentTo(
        [$usuario],
        NotificacionImportante::class
    );
}

Al utilizar Notification::fake(), se evita que se envíen notificaciones reales durante la prueba, permitiendo verificar que se haya intentado enviar la notificación correcta al usuario esperado.

En cuanto a la gestión de eventos y trabajos en cola, es posible desactivarlos temporalmente durante las pruebas para centrarse en el comportamiento específico:

public function testProcesoSinEventos()
{
    Event::fake();

    // Acción que dispararía eventos
    $this->post('/procesar-datos');

    // Verificar que no se dispararon eventos no deseados
    Event::assertNotDispatched(EventoIndeseado::class);
}

De forma similar, para trabajos en cola:

public function testTrabajoEnCola()
{
    Bus::fake();

    // Acción que debería despachar un trabajo
    $this->post('/generar-reporte');

    // Verificar que se despachó el trabajo esperado
    Bus::assertDispatched(GenerarReporte::class);
}

Estas herramientas permiten un control preciso sobre el comportamiento de la aplicación durante las pruebas, asegurando que solo se ejecuten las partes relevantes para cada caso.

Finalmente, para ejecutar las pruebas, se utiliza el siguiente comando en la terminal:

php artisan test

Este comando ejecuta todas las pruebas ubicadas en el directorio tests, mostrando un resumen de los resultados y facilitando la identificación de errores o pruebas fallidas.

El uso adecuado de TestCase y la integración con la base de datos permiten desarrollar pruebas de integración completas, que aseguran el correcto funcionamiento de la aplicación y mejoran la calidad del código en proyectos Laravel.

Pruebas de rutas y controladores

Las pruebas de rutas y controladores son esenciales para garantizar que la aplicación responda correctamente a las solicitudes HTTP y que los controladores gestionen adecuadamente la lógica de negocio. Laravel ofrece herramientas para simular solicitudes y verificar respuestas, facilitando el testing de estos componentes.

Para probar una ruta, se pueden utilizar los métodos proporcionados por la clase TestCase, como get, post, put, patch y delete. Estos métodos permiten enviar solicitudes HTTP a rutas específicas y analizar las respuestas. Por ejemplo:

public function testRutaInicioDevuelveVistaCorrecta()
{
    $response = $this->get('/');

    $response->assertStatus(200);
    $response->assertViewIs('welcome');
}

En este caso, se envía una solicitud GET a la ruta '/' y se verifica que el estado HTTP sea 200 y que se esté devolviendo la vista 'welcome'.

Para rutas que aceptan parámetros, es posible pasarlos directamente en la URL o como segundo argumento del método de solicitud:

public function testRutaUsuarioMuestraInformacionCorrecta()
{
    $usuario = Usuario::factory()->create();

    $response = $this->get('/usuarios/' . $usuario->id);

    $response->assertStatus(200);
    $response->assertViewHas('usuario', $usuario);
}

Aquí, se crea un usuario de prueba y se verifica que al acceder a la ruta correspondiente, la vista reciba el objeto usuario con los datos esperados.

Al probar controladores, es importante verificar no solo la respuesta, sino también que la lógica interna se ejecute de manera adecuada. Por ejemplo, al probar un método que crea un nuevo recurso:

public function testControladorGuardaNuevoProducto()
{
    $datosProducto = [
        'nombre' => 'Producto de prueba',
        'precio' => 19.99,
        'descripcion' => 'Descripción del producto de prueba',
    ];

    $response = $this->post('/productos', $datosProducto);

    $response->assertRedirect('/productos');
    $this->assertDatabaseHas('productos', ['nombre' => 'Producto de prueba']);
}

En este ejemplo, se envía una solicitud POST con datos de un nuevo producto y se comprueba que la aplicación redirige a la ruta /productos y que el producto ha sido guardado en la base de datos. Se utiliza el método assertDatabaseHas para verificar que existe un registro con el nombre especificado.

Para rutas que requieren autenticación o ciertos permisos, se puede simular un usuario autenticado utilizando el método actingAs:

public function testRutaAdministracionAccesibleParaAdmin()
{
    $admin = Usuario::factory()->create(['rol' => 'admin']);

    $response = $this->actingAs($admin)->get('/admin');

    $response->assertStatus(200);
    $response->assertSee('Panel de administración');
}

Aquí, se simula que un usuario con rol de administrador accede a la ruta /admin y se verifica que el estado sea 200 y que la respuesta contiene el texto esperado.

Es posible probar redirecciones y mensajes flash que los controladores puedan establecer:

public function testRedireccionDespuesDeEliminarUsuario()
{
    $usuario = Usuario::factory()->create();

    $response = $this->actingAs($usuario)->delete('/usuarios/' . $usuario->id);

    $response->assertRedirect('/usuarios');
    $response->assertSessionHas('mensaje', 'Usuario eliminado correctamente.');
    $this->assertDatabaseMissing('usuarios', ['id' => $usuario->id]);
}

En este caso, se verifica que después de eliminar un usuario, la aplicación redirija a la lista de usuarios, establezca un mensaje en la sesión y que el usuario ya no exista en la base de datos.

Para aplicaciones que emplean API y devuelven respuestas en formato JSON, es posible probar el contenido de la respuesta utilizando métodos como assertJson y assertJsonFragment:

public function testApiListaProductosDevuelveDatosCorrectos()
{
    $productos = Producto::factory()->count(3)->create();

    $response = $this->getJson('/api/productos');

    $response->assertStatus(200);
    $response->assertJsonCount(3);
    $response->assertJsonFragment(['nombre' => $productos->first()->nombre]);
}

Aquí, se comprueba que la ruta de la API devuelve un JSON con tres productos y que uno de ellos contiene el nombre esperado.

Cuando se prueban rutas con middleware, es importante verificar que los usuarios sin los permisos adecuados no puedan acceder:

public function testRutaAdminNoAccesibleParaUsuariosNoAutenticados()
{
    $response = $this->get('/admin');

    $response->assertRedirect('/login');
}

public function testRutaAdminNoAccesibleParaUsuariosNoAutorizados()
{
    $usuario = Usuario::factory()->create(['rol' => 'usuario']);

    $response = $this->actingAs($usuario)->get('/admin');

    $response->assertStatus(403);
}

En el primer caso, un usuario no autenticado es redirigido al login, y en el segundo, un usuario sin permisos recibe un estado 403 Forbidden.

También es posible probar la validación en los controladores al enviar datos inválidos:

public function testCreacionDeUsuarioConDatosInvalidos()
{
    $datosInvalidos = [
        'nombre' => '',
        'email' => 'email-no-valido',
        'password' => '123',
    ];

    $response = $this->post('/usuarios', $datosInvalidos);

    $response->assertSessionHasErrors(['nombre', 'email', 'password']);
    $this->assertDatabaseCount('usuarios', 0);
}

Aquí, se envían datos inválidos y se verifica que la sesión contenga errores para los campos nombre, email y password, además de comprobar que no se ha creado ningún usuario en la base de datos.

Para rutas que utilizan parámetros opcionales o que pueden devolver diferentes vistas según la entrada, es importante probar varias combinaciones:

public function testBusquedaConResultados()
{
    $producto = Producto::factory()->create(['nombre' => 'Producto Especial']);

    $response = $this->get('/buscar?query=Especial');

    $response->assertStatus(200);
    $response->assertSee('Producto Especial');
}

public function testBusquedaSinResultados()
{
    $response = $this->get('/buscar?query=Inexistente');

    $response->assertStatus(200);
    $response->assertSee('No se han encontrado resultados.');
}

De esta forma, se asegura que la ruta de búsqueda funciona correctamente tanto cuando hay resultados como cuando no.

Para las rutas nombradas, se puede utilizar el método route para generar la URL dentro de las pruebas:

public function testAccesoARutaNombrada()
{
    $response = $this->get(route('contacto'));

    $response->assertStatus(200);
    $response->assertViewIs('contacto');
}

Esto es útil especialmente si las rutas cambian en el futuro, ya que las pruebas seguirán siendo válidas al utilizar los nombres de ruta.

Al probar controladores que dependen de servicios externos o que interactúan con otros componentes, se pueden utilizar mocks para simular su comportamiento:

public function testControladorEnviaNotificacion()
{
    Mail::fake();

    $datosFormulario = [
        'email' => 'usuario@example.com',
        'mensaje' => 'Mensaje de prueba',
    ];

    $response = $this->post('/contacto', $datosFormulario);

    $response->assertRedirect('/contacto');
    Mail::assertSent(ContactoMail::class, function ($mail) use ($datosFormulario) {
        return $mail->hasTo('soporte@example.com') &&
               $mail->mensaje === $datosFormulario['mensaje'];
    });
}

En este ejemplo, se finge el envío de correos para verificar que el controlador envía la notificación correcta sin realmente enviar emails durante la prueba.

Pruebas de formularios y validaciones

Las pruebas de formularios y validaciones son fundamentales para asegurar que la aplicación maneja correctamente la entrada del usuario y ofrece retroalimentación adecuada ante datos inválidos. En Laravel, es posible simular el envío de formularios y verificar que las reglas de validación se aplican correctamente, garantizando una experiencia de usuario consistente.

Para probar formularios, se utiliza el método post, put o patch de la clase TestCase, dependiendo del método HTTP que el formulario utilice. Se pueden enviar datos válidos e inválidos para comprobar el comportamiento de la aplicación en ambos casos.

Por ejemplo, para probar que un formulario de registro acepta datos válidos y crea un nuevo usuario:

public function testRegistroConDatosValidos()
{
    $datosValidos = [
        'nombre' => 'María',
        'email' => 'maria@example.com',
        'password' => 'ContraseñaSegura123',
        'password_confirmation' => 'ContraseñaSegura123',
    ];

    $response = $this->post('/register', $datosValidos);

    $response->assertRedirect('/home');
    $this->assertAuthenticated();
    $this->assertDatabaseHas('users', [
        'email' => 'maria@example.com',
    ]);
}

En este test, se envía una solicitud POST a la ruta /register con datos válidos, se verifica que la aplicación redirige al usuario a /home, que el usuario está autenticado, y que los datos han sido guardados en la base de datos.

Para comprobar que la validación funciona correctamente con datos inválidos:

public function testRegistroConDatosInvalidos()
{
    $datosInvalidos = [
        'nombre' => '',
        'email' => 'correo-no-valido',
        'password' => '123',
        'password_confirmation' => '456',
    ];

    $response = $this->from('/register')->post('/register', $datosInvalidos);

    $response->assertRedirect('/register');
    $response->assertSessionHasErrors([
        'nombre',
        'email',
        'password',
    ]);
    $this->assertGuest();
}

Aquí, se utiliza el método from para indicar la página previa, y se espera que la aplicación redirija de vuelta al formulario con los errores de validación en sesión. Se verifica que los campos nombre, email y password han fallado la validación, y que el usuario no ha sido autenticado.

Es importante comprobar que los mensajes de error se muestran correctamente en la vista. Para ello, se puede utilizar el método assertSee para buscar en la respuesta los mensajes esperados:

public function testMensajesDeErrorEnVista()
{
    $datosInvalidos = [
        'nombre' => '',
        'email' => 'correo-no-valido',
        'password' => '123',
        'password_confirmation' => '456',
    ];

    $response = $this->post('/register', $datosInvalidos);

    $response->assertSee('El campo nombre es obligatorio.');
    $response->assertSee('El campo email debe ser una dirección de correo válida.');
    $response->assertSee('La confirmación de contraseña no coincide.');
}

Sin embargo, en muchos casos los mensajes de error se muestran en la vista mediante variables de sesión o traducciones, por lo que es más fiable utilizar assertSessionHasErrors.

Además, es útil verificar que los datos ingresados por el usuario se retienen para evitar que tenga que volver a introducirlos. Laravel automáticamente almacena los old inputs en sesión. En los tests, se puede comprobar que los datos antiguos están presentes en la vista:

public function testOldInputsSeMantienen()
{
    $datosInvalidos = [
        'nombre' => 'Ana',
        'email' => 'correo-no-valido',
        'password' => '123',
        'password_confirmation' => '456',
    ];

    $response = $this->post('/register', $datosInvalidos);

    $response->assertSessionHasInput('nombre', 'Ana');
    $response->assertSessionHasInput('email', 'correo-no-valido');
}

Para aplicaciones que utilizan Form Requests, es posible que queramos probar las reglas de validación de forma aislada. Laravel permite instanciar un Form Request y validar datos directamente en los tests:

public function testReglasDeValidacionDelRegistro()
{
    $request = new \App\Http\Requests\RegisterRequest();

    $validator = \Validator::make([
        'nombre' => '',
        'email' => 'correo-no-valido',
        'password' => '123',
    ], $request->rules());

    $this->assertTrue($validator->fails());
    $this->assertEquals(['nombre', 'email', 'password'], array_keys($validator->failed()));
}

De esta manera, se puede verificar que las reglas de validación definidas en el Form Request funcionan como se espera sin necesidad de hacer una solicitud HTTP completa.

Cuando se trabaja con formularios que incluyen subida de archivos, es importante simular el proceso de carga en las pruebas. Laravel proporciona el método UploadedFile::fake() para crear archivos falsos que se pueden utilizar en los tests:

public function testSubidaDeAvatar()
{
    Storage::fake('avatars');

    $archivo = UploadedFile::fake()->image('avatar.jpg');

    $response = $this->actingAs($user)->post('/perfil', [
        'avatar' => $archivo,
    ]);

    $response->assertStatus(302);
    Storage::disk('avatars')->assertExists($archivo->hashName());
}

En este ejemplo, se finge el sistema de almacenamiento para evitar escribir en el disco real, se crea un archivo de imagen falso y se envía en el formulario. Luego, se verifica que el archivo ha sido guardado en el disco apropiado.

Para probar formularios AJAX o solicitudes que esperan una respuesta JSON, se pueden utilizar los métodos postJson, putJson, etc., y luego verificar la estructura del JSON de respuesta:

public function testValidacionDeApi()
{
    $datosInvalidos = [
        'email' => 'correo-no-valido',
        'password' => '123',
    ];

    $response = $this->postJson('/api/login', $datosInvalidos);

    $response->assertStatus(422);
    $response->assertJsonValidationErrors(['email', 'password']);
}

Aquí, se verifica que la API devuelve un estado 422 Unprocessable Entity y que los errores de validación se incluyen en el JSON de respuesta.

En casos donde se definen reglas de validación personalizadas, es fundamental incluir pruebas que cubran estas reglas específicas. Por ejemplo:

public function testValidacionPersonalizadaDeTelefono()
{
    $datosInvalidos = [
        'telefono' => '123abc',
    ];

    $response = $this->post('/contacto', $datosInvalidos);

    $response->assertSessionHasErrors(['telefono']);
}

public function testValidacionExitosaDeTelefono()
{
    $datosValidos = [
        'telefono' => '612345678',
    ];

    $response = $this->post('/contacto', $datosValidos);

    $response->assertSessionHasNoErrors();
    $response->assertRedirect('/gracias');
}

En estos tests, se comprueba que la regla personalizada para el campo telefono funciona correctamente con datos válidos e inválidos.

Para formularios que utilizan CAPTCHA u otras medidas antispam, se debe considerar cómo simular o desactivar estas protecciones durante las pruebas.

Algunos componentes, como el token CSRF, son manejados automáticamente por Laravel en las pruebas, por lo que no es necesario incluirlos manualmente.

Cuando los formularios incluyen lógicas más complejas, como creación de múltiples registros o asociaciones, es importante verificar que todos los datos se han guardado adecuadamente:

public function testCreacionDePedidoConProductos()
{
    $producto1 = Producto::factory()->create();
    $producto2 = Producto::factory()->create();

    $datosPedido = [
        'cliente_id' => $cliente->id,
        'productos' => [
            ['producto_id' => $producto1->id, 'cantidad' => 2],
            ['producto_id' => $producto2->id, 'cantidad' => 1],
        ],
    ];

    $response = $this->post('/pedidos', $datosPedido);

    $response->assertRedirect('/pedidos');
    $this->assertDatabaseHas('pedidos', ['cliente_id' => $cliente->id]);
    $this->assertDatabaseHas('pedido_producto', [
        'producto_id' => $producto1->id,
        'cantidad' => 2,
    ]);
}

En este ejemplo, se comprueba que tanto el pedido como las relaciones en la tabla pivote se han creado correctamente.

Es esencial también probar que los formularios manejan adecuadamente situaciones como datos duplicados, límites de entrada, y que las reglas de negocio se respetan.

Por último, las pruebas de formularios y validaciones deben ser exhaustivas para cubrir todas las posibles rutas que un usuario podría tomar al interactuar con los formularios de la aplicación, asegurando que el sistema es robusto y fiable.

Testing de autenticación y middleware

Las pruebas de autenticación y middleware son esenciales para garantizar que la seguridad y las restricciones de acceso en la aplicación funcionan correctamente. Laravel proporciona herramientas para simular usuarios autenticados y verificar que el middleware aplica las reglas adecuadas en cada caso.

Para probar el proceso de inicio de sesión, se puede simular el envío del formulario de acceso y verificar que el usuario es autenticado correctamente. Por ejemplo:

public function testUsuarioPuedeIniciarSesionConCredencialesCorrectas()
{
    $usuario = Usuario::factory()->create([
        'email' => 'correo@example.com',
        'password' => bcrypt('contraseña-segura'),
    ]);

    $response = $this->post('/login', [
        'email' => 'correo@example.com',
        'password' => 'contraseña-segura',
    ]);

    $response->assertRedirect('/home');
    $this->assertAuthenticatedAs($usuario);
}

En este test, se crea un usuario de prueba y se simula el envío del formulario de inicio de sesión con las credenciales correctas. Se verifica que la aplicación redirige a /home y que el usuario ha sido autenticado.

También es importante comprobar que el sistema maneja adecuadamente las credenciales incorrectas:

public function testUsuarioNoPuedeIniciarSesionConCredencialesIncorrectas()
{
    $usuario = Usuario::factory()->create([
        'email' => 'correo@example.com',
        'password' => bcrypt('contraseña-segura'),
    ]);

    $response = $this->from('/login')->post('/login', [
        'email' => 'correo@example.com',
        'password' => 'contraseña-incorrecta',
    ]);

    $response->assertRedirect('/login');
    $response->assertSessionHasErrors('email');
    $this->assertGuest();
}

Aquí, se verifica que, al proporcionar una contraseña incorrecta, el usuario no es autenticado, se redirige de vuelta al formulario de login y se muestran los errores adecuados.

Para probar la funcionalidad de cierre de sesión, se puede simular una petición al logout:

public function testUsuarioPuedeCerrarSesion()
{
    $usuario = Usuario::factory()->create();

    $response = $this->actingAs($usuario)->post('/logout');

    $response->assertRedirect('/');
    $this->assertGuest();
}

En este caso, se simula que un usuario autenticado envía una solicitud POST a /logout, y se verifica que es redirigido a '/' y que ya no está autenticado.

Respecto al middleware, es crucial probar que las rutas protegidas requieren autenticación y que los usuarios sin los permisos adecuados no pueden acceder. Por ejemplo:

public function testRutaProtegidaRequiereAutenticacion()
{
    $response = $this->get('/perfil');

    $response->assertRedirect('/login');
}

public function testUsuarioAutenticadoPuedeAccederARutaProtegida()
{
    $usuario = Usuario::factory()->create();

    $response = $this->actingAs($usuario)->get('/perfil');

    $response->assertStatus(200);
    $response->assertViewIs('perfil');
}

El primer test comprueba que un usuario no autenticado es redirigido al login al intentar acceder a /perfil. El segundo verifica que un usuario autenticado puede acceder sin problemas.

Si la aplicación utiliza middleware para gestionar roles o permisos, se pueden crear usuarios con diferentes roles y comprobar el acceso:

public function testUsuarioAdminPuedeAccederAPanelDeAdministracion()
{
    $admin = Usuario::factory()->create([
        'rol' => 'admin',
    ]);

    $response = $this->actingAs($admin)->get('/admin');

    $response->assertStatus(200);
    $response->assertSee('Panel de Administración');
}

public function testUsuarioNoAutorizadoNoPuedeAccederAPanelDeAdministracion()
{
    $usuario = Usuario::factory()->create([
        'rol' => 'usuario',
    ]);

    $response = $this->actingAs($usuario)->get('/admin');

    $response->assertStatus(403);
}

Aquí, se verifica que solo los usuarios con rol 'admin' pueden acceder al panel de administración, mientras que otros usuarios reciben un estado 403 Forbidden.

Para probar middleware personalizados, es posible simular su comportamiento y verificar que aplican correctamente las restricciones. Por ejemplo, si se tiene un middleware que verifica que el usuario ha completado su perfil:

public function testUsuarioSinPerfilCompletoEsRedirigido()
{
    $usuario = Usuario::factory()->create([
        'perfil_completo' => false,
    ]);

    $response = $this->actingAs($usuario)->get('/dashboard');

    $response->assertRedirect('/completar-perfil');
}

public function testUsuarioConPerfilCompletoPuedeAcceder()
{
    $usuario = Usuario::factory()->create([
        'perfil_completo' => true,
    ]);

    $response = $this->actingAs($usuario)->get('/dashboard');

    $response->assertStatus(200);
    $response->assertViewIs('dashboard');
}

Estos tests aseguran que el middleware personalizado redirige a los usuarios que no han completado su perfil y permite el acceso a quienes sí lo han hecho.

Cuando se quiere probar una ruta sin la interferencia del middleware, se puede utilizar el método withoutMiddleware:

public function testAccesoARutaSinMiddleware()
{
    $response = $this->withoutMiddleware()->get('/perfil');

    $response->assertStatus(200);
}

Esto es útil para enfocarse en probar la funcionalidad de la ruta en sí, sin las restricciones del middleware. Sin embargo, en pruebas de integración es recomendable mantener el middleware activo para simular el comportamiento real de la aplicación.

Por otro lado, si se necesita asegurarse de que el middleware está activo en una prueba específica, se puede utilizar withMiddleware:

public function testMiddlewareEstaActivo()
{
    $this->withMiddleware();

    $response = $this->get('/perfil');

    $response->assertRedirect('/login');
}

Este método es útil si en la configuración de pruebas se ha desactivado el middleware por defecto y se quiere activar para pruebas específicas.

Para pruebas relacionadas con API que utilizan autenticación, como tokens o JWT, se pueden establecer los encabezados adecuados:

public function testApiProtegidaRequiereToken()
{
    $response = $this->getJson('/api/datos');

    $response->assertStatus(401);
}

public function testApiProtegidaConTokenValido()
{
    $usuario = Usuario::factory()->create([
        'api_token' => 'token-valido',
    ]);

    $response = $this->getJson('/api/datos', [
        'Authorization' => 'Bearer token-valido',
    ]);

    $response->assertStatus(200);
}

En estos casos, se verifica que las rutas de la API protegidas requieren un token válido y responden adecuadamente.

Además, es posible probar que el middleware registra correctamente la actividad del usuario o registra logs. Para middleware que realizan acciones específicas, se puede utilizar el spy de facades:

public function testMiddlewareRegistraActividad()
{
    Log::spy();

    $usuario = Usuario::factory()->create();

    $this->actingAs($usuario)->get('/perfil');

    Log::shouldHaveReceived('info')
        ->with('Acceso al perfil', ['usuario_id' => $usuario->id])
        ->once();
}

Este test comprueba que el middleware registra una entrada en el log cuando un usuario accede a su perfil.

Para middleware que manipulan la respuesta, se puede verificar que los cambios se aplican:

public function testMiddlewareAgregaEncabezado()
{
    $response = $this->get('/pagina');

    $response->assertHeader('X-Middleware', 'Aplicado');
}

En este ejemplo, se comprueba que el middleware añade un encabezado específico a la respuesta.

Cuando se trabaja con gateway o middleware de terceros, es importante simular las condiciones necesarias para que su funcionamiento sea probado correctamente. Por ejemplo, para middleware que depende de ciertas configuraciones:

public function testMiddlewareConConfiguracion()
{
    Config::set('opciones.opcion_activa', true);

    $usuario = Usuario::factory()->create();

    $response = $this->actingAs($usuario)->get('/ruta-protegida');

    $response->assertStatus(200);
}

Aquí, se ajusta la configuración antes de ejecutar el test para asegurar que el middleware se comporta según lo esperado.

En el caso de querer probar el rendimiento o impacto de varios middleware en una ruta, se pueden utilizar los métodos withMiddleware y withoutMiddleware en conjunto:

public function testRutaConMiddlewareEsMasLenta()
{
    $inicioConMiddleware = microtime(true);

    $this->withMiddleware()->get('/ruta');

    $duracionConMiddleware = microtime(true) - $inicioConMiddleware;

    $inicioSinMiddleware = microtime(true);

    $this->withoutMiddleware()->get('/ruta');

    $duracionSinMiddleware = microtime(true) - $inicioSinMiddleware;

    $this->assertGreaterThan($duracionSinMiddleware, $duracionConMiddleware);
}

Este ejemplo demuestra cómo se puede medir el impacto del middleware en el tiempo de respuesta de una ruta.

Es fundamental probar que el middleware respeta las excepciones o condiciones especiales. Si existe una ruta exenta de cierto middleware, se debe verificar:

public function testRutaExentaDeMiddleware()
{
    $response = $this->get('/ruta-exenta');

    $response->assertStatus(200);
}

Al probar la autenticación y middleware, se asegura que el flujo de la aplicación es seguro y que las restricciones de acceso funcionan correctamente, garantizando la integridad y seguridad de la aplicación en entornos reales.

Mejores prácticas en pruebas de integración

En las pruebas de integración es fundamental seguir buenas prácticas para asegurar que las pruebas sean eficaces, mantenibles y fiables. A continuación, se presentan algunas recomendaciones clave para mejorar la calidad de tus pruebas de integración en Laravel.

  • Mantener las pruebas independientes es esencial. Cada prueba debe ser autónoma y no depender del estado dejado por otras pruebas. Utiliza el trait RefreshDatabase para asegurar que la base de datos se reinicia entre pruebas, garantizando un entorno consistente.
  • Es recomendable utilizar factories y seeders para generar datos de prueba. Esto facilita la creación de escenarios realistas y evita la dependencia de datos estáticos o hardcodeados, mejorando la flexibilidad de las pruebas.
  • Implementa nombres de pruebas descriptivos que reflejen el propósito y el resultado esperado. Un buen nombre debe indicar claramente qué se está probando y bajo qué condiciones, facilitando la comprensión y mantenimiento del código.
  • Utiliza las aserciones adecuadas proporcionadas por Laravel y PHPUnit. Aserciones como assertStatus, assertSee, assertDatabaseHas, entre otras, permiten verificar distintos aspectos de la respuesta y el estado de la aplicación de forma precisa.
  • Evita probar múltiples funcionalidades en una sola prueba. Las pruebas deben ser enfocadas y centrarse en un solo comportamiento o caso de uso. Esto facilita la identificación de errores y mejora la legibilidad del suite de pruebas.
  • Aprovecha las herramientas de Laravel para simular usuarios y autenticación. Utiliza métodos como actingAs para probar rutas y funcionalidades que requieren un usuario autenticado, asegurando que los permisos y roles se manejan correctamente.
  • Cuando sea necesario, mockea servicios externos para evitar dependencias de terceros y reducir el tiempo de ejecución de las pruebas. Laravel ofrece facilidades para el mockeo de eventos, notificaciones y trabajos en cola, lo cual es útil para pruebas más eficientes.
  • Configura un entorno de pruebas adecuado, utilizando una base de datos en memoria o una base de datos específica para pruebas. Esto evita conflictos con los datos de desarrollo y producción, y asegura que las pruebas no afecten otros entornos.
  • Incorpora las pruebas en tu flujo de trabajo utilizando integración continua. Automatiza la ejecución de las pruebas en cada commit o push, lo que permite detectar errores de forma temprana y mantener la calidad del código en todo momento.
  • Optimiza el rendimiento de las pruebas evitando cargas innecesarias. Por ejemplo, si no necesitas ejecutar middleware en ciertas pruebas, puedes desactivarlo con $this->withoutMiddleware() para acelerar la ejecución.
  • Mantén el código de las pruebas limpio y ordenado. Aplica principios como DRY (Don't Repeat Yourself) creando métodos auxiliares o traits que puedan reutilizarse en múltiples pruebas, lo que reduce la duplicación y facilita el mantenimiento.
  • Actualiza las pruebas cuando el código de producción cambie. Las pruebas deben reflejar el estado actual de la aplicación. Evita dejar pruebas obsoletas o que fallen debido a cambios no reflejados, para mantener la confiabilidad del suite de pruebas.
  • Revisa regularmente la cobertura de código para identificar áreas no cubiertas por las pruebas. Herramientas como Xdebug y PHPUnit Code Coverage pueden ser útiles para generar informes detallados y mejorar la cobertura.
  • Documenta las pruebas más complejas. Aunque el código de las pruebas debe ser claro, en ocasiones es útil añadir comentarios que expliquen el propósito o la lógica detrás de ciertas implementaciones, facilitando la colaboración en equipo.
  • Asegúrate de que las pruebas sean repetibles y no dependan del orden de ejecución. Esto implica evitar dependencias entre pruebas y utilizar métodos como setUp y tearDown para inicializar y limpiar el estado antes y después de cada prueba.
  • Considera el uso de data providers para probar múltiples casos con distintos conjuntos de datos. Esto permite escribir pruebas más robustas y detectar comportamientos inesperados con diferentes entradas de forma eficiente.
  • Utiliza las facades de fakes de Laravel, como Mail::fake() y Notification::fake(), para evitar enviar correos electrónicos o notificaciones reales durante las pruebas, y poder verificar que se envían correctamente.
  • No ignores las advertencias o errores en las pruebas. Cada fallo debe ser investigado y solucionado para mantener la integridad del suite de pruebas y asegurar que todos los casos están correctamente cubiertos.
  • Fomenta una cultura de pruebas continuas en el equipo de desarrollo. Las pruebas deben ser una parte integral del proceso de desarrollo, no una tarea secundaria o posterior, promoviendo una mayor calidad y confiabilidad en el software.

Para seguir leyendo hazte Plus

¿Ya eres Plus? Accede a la app

Plan mensual

19.00 € /mes

Precio normal mensual: 19 €
47 % DE DESCUENTO

Plan anual

10.00 € /mes

Ahorras 108 € al año
Precio normal anual: 120 €
Aprende Laravel GRATIS online

Todas las lecciones de Laravel

Accede a todas las lecciones de Laravel y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a Laravel y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Entender el propósito y diferencias de las pruebas de integración.
  • Configurar el entorno de pruebas con TestCase en Laravel.
  • Simular solicitudes HTTP y verificar respuestas.
  • Iteractuar con bases de datos durante las pruebas.
  • Probar la interacción entre controladores, modelos y vistas.
  • Utilizar factories y seeders para preparar datos de prueba.
  • Verificar validaciones de formularios y manejo de errores.
  • Simular autenticaciones y middleware en pruebas.