logo cosasdedevs
Parte 6: Test unitarios (unit test) en nuestro blog con LARAVEL 8

Parte 6: Test unitarios (unit test) en nuestro blog con LARAVEL 8



My Profile
Ene 20, 2021

Por fin llegamos a la última parte de esta serie de tutoriales sobre como crear un BLOG con LARAVEL 8 😁. Para esta última parte, vamos a aprender como realizar test unitarios en nuestro blog para verificar que funciona correctamente.

Pero antes de nada para el que no lo tenga claro...

¿Qué son los tests unitarios o unittests?

Los tests unitarios (unit test) son scripts que generamos para verificar el correcto funcionamiento de un fragmento de nuestro código. Esto nos servirá para corregir posibles errores cuando realicemos modificaciones en nuestros proyectos que si no los implementásemos puede que se nos escapasen. Si es la primera vez que oyes hablar de los test unitarios, te recomiendo que te pases antes por este tutorial donde explico cómo crearlos desde 0 con PHP 😎.

¿Cómo crear test unitarios con Laravel 8?

Laravel 8 tiene incluido por defecto el soporte para trabajar con PHPUnit. Si nos fijamos, en la raíz de nuestro proyecto, tenemos una carpeta llamada tests. Esta a su vez tiene dos carpetas llamadas Feature e Unit.

Diferencias entre Feature e Unit

La carpeta Unit se utiliza para guardar tests muy específicos sobre un fragmento de código y en Feature guardaremos los test en los que realizaremos comprobaciones más complejas.

Configuraciones antes de crear nuestros tests

Para evitar la eliminación de datos de forma errónea aunque sea de nuestra BBDD local. Laravel nos permite crear una configuración adicionar para realizar nuestros tests. De esta forma podremos crear una segunda configuración para una base de datos de prueba y todas las acciones las realizará sobre esa BBDD.

Para realizar esta acción, duplicaremos nuestro archivo .env y lo renombraremos como .env.testing.

Ahora debemos crear una nueva BBDD y añadirla en el archivo .env.testing de tal forma que quede algo parecido a esto:

.
.
.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_blog_test
DB_USERNAME=test
DB_PASSWORD=test

.
.
.

También realizaremos una modificación en el archivo database/factories/UserFactory.php. Lo abrimos y modificamos el método definition() para que ahora retorne este array:

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'name' => $this->faker->name,
            'email' => $this->faker->unique()->safeEmail,
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
            'is_admin' => true,
            'is_staff' => true,
        ];
    }

De esta forma, cuando utilicemos la clase UserFactory para crear usuarios, estos tendrán permisos de administración y staff.

Crear un test con Laravel 8

Crear un test con Laravel es muy sencillo, al igual que para crear otros archivos, Laravel tiene un comando que nos crea la base del archivo por lo que nos ahorramos ese paso. Como primer test, nos vamos a encargar de testar el tema de comentarios así que para crear nuestro test sobre comentarios, lanzaremos el siguiente comando:

php artisan make:test CommentTest

Este comando nos creará un archivo llamado CommentTest.php dentro de tests/Feature/. Ahora lo que debemos hacer es abrir el archivo y reemplazar su código por el siguiente:

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use App\Models\User;
use App\Models\Post;
use App\Models\Comment;

class CommentTest extends TestCase
{
    /**
     * A basic feature test example.
     *
     * @return void
     */
    public function testCreateComment()
    {
        $user = User::factory()->create();
        $test_post = [
            'title' => 'My test post with comment',
            'body' => 'This is a test functional post',
            'is_draft' => false
        ];
        $response = $this->actingAs($user)->post('/admin/posts', $test_post);
        $response->assertSessionHas('status', 'Post has been created sucessfully');

        $post = Post::where('title', $test_post['title'])->first();

        $comment = 'This is a test comment';
        $test_comment = [
            'post_id' => $post->id,
            'comment' => $comment
        ];
        $response = $this->actingAs($user)->post('/comment', $test_comment);
        $response->assertSessionHas('status', 'Comment has been created sucessfully');

        $comment = Comment::where('user_id', $user->id)
        ->where('post_id', $post->id)
        ->where('comment', $comment)->first();

        $this->assertNotNull($comment);

        $this->post('/logout');

        // Ahora probamos con un usuario sin loguear
        $response = $this->post('/comment', $test_comment);
        $response->assertStatus(403);
    }
}

Este test, primero crea un usuario. Para ello, utilizamos la clase UserFactory que modificamos anteriormente para que el usuario que creemos tenga los permisos adecuados.

Lo siguiente que hacemos es crear un post. Para ello, realizaremos una simulación de la petición gracias a las funciones que nos provee la herramienta de testing de Laravel.

La creación del post se realiza con la siguiente línea:

$response = $this->actingAs($user)->post('/admin/posts', $test_post);

Con el método actingAs, le decimos que simule que la petición la está realizando el usuario que pasamos por parámetro. Esto quiere decir, que simulará que ese usuario está logueado al realizar la petición. El siguiente método es el método post. Ya que para crear un post necesitamos realizar una petición de ese tipo, usamos ese método.

Nota: Si fuera get, usariamos get(), con delete, delete(), etc...

En el método post, como primer parámetro, pasamos la url para crear un post (si no recordáis una ruta podéis usar el comando php artisan route:list para listarlas) y como segundo parámetro los datos que queremos enviar para crear el post. En este caso el array $test_post que contiene toda la información necesaria para ella.

Si todo ha ido bien, debería retornarnos la variable de sesión status que devolvemos a crear un post con el mensaje que añadimos. Para verificar que existe y que contiene el mensaje que creamos, utilizamos la siguiente línea de código:

$response->assertSessionHas('status', 'Post has been created sucessfully');

El siguiente paso es recuperar el post, ya que necesitaremos su id.

Al igual que con el post, lanzamos la petición para crear el comentario y comprobamos con assertSessionHas que tenemos el mensaje de creación del comentario.

Aparte de esta comprobación, revisamos si existe en la base de datos y confirmamos con assertNotNull que la búsqueda del comentario ha dado un resultado.

Para finalizar, realizamos la prueba de creación de un comentario sin usuario para verificar que no lo permite. Para realizar esta acción, eliminamos el método actingAs de la petición y de esta forma se realizará como un usuario no logueado.

Comprobamos que la petición de como resultado de estado el valor 403 que es el http status de prohibido y con esto terminaríamos los test para los comentarios.

Ahora que ya está explicado, vamos a lanzar los tests para verificar que todo está ok. Para ello, lanzaremos el siguiente comando:

php artisan test

Si habéis seguido todos los pasos, debería mostrar por consola que todos los tests han pasado, si no es así, revisad o poned un comentario y os echo una mano 🖐.

Como ejemplo, también os voy a dejar unos tests que realice para probar todas las funcionalidades de los posts pero esta vez no lo voy a explicar, ya que es bastante similar a lo visto en los test para los comentarios.

Para testar los posts, primero creamos el archivo PostTest:

php artisan make:test PostTest

Y después solo necesitamos añadir el siguiente código y ya los tendréis 💪

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use App\Models\User;
use App\Models\Post;
use \Cviebrock\EloquentSluggable\Services\SlugService;

class PostTest extends TestCase
{
    use RefreshDatabase;
    /**
     * A basic feature test example.
     *
     * @return void
     */
    public function testMainPage()
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }

    /**
     * Testing create posts and checked if exists and can be visualed
     *
     * @return void
     */
    public function testCreatePost()
    {
        $user = User::factory()->create();
        $title = 'My test post';
        $response = $this->actingAs($user)->post('/admin/posts', [
            'title' => $title,
            'body' => 'This is a test functional post',
            'is_draft' => false
        ]);
        $response->assertSessionHas('status', 'Post has been created sucessfully');
        $response->assertRedirect();

        // Con unique == false no revisa en la base posts y entonces no se raya con que sea un duplicado
        $slug_url = SlugService::createSlug(Post::class, 'slug', $title, ['unique' => false]);

        $response = $this->get('/posts/' . $slug_url);
        $response->assertStatus(200);

        $title .= '2';
        $response = $this->actingAs($user)->post('/admin/posts', [
            'title' => $title,
            'body' => 'This is a test functional post',
            'is_draft' => true
        ]);
        $response->assertSessionHas('status', 'Post has been created sucessfully');
        $response->assertRedirect();

        // Con unique == false no revisa en la base posts y entonces no se raya con que sea un duplicado
        $slug_url = SlugService::createSlug(Post::class, 'slug', $title, ['unique' => false]);

        $response = $this->get('/posts/' . $slug_url);
        $response->assertStatus(404);

        $response = $this->actingAs($user)->post('/admin/posts', []);
        $response->assertSessionHasErrors([
            'title' => 'A title is required',
            'body' => 'You must sent a body',
            'is_draft' => 'You must sent if is draft or not'
        ]);
    }

    /**
     * Testing create posts and checked if exists and can be visualed
     *
     * @return void
     */
    public function testEditPost()
    {
        $user = User::factory()->create();
        $test_post = [
            'title' => 'test edit post',
            'body' => 'This is a test functional post',
            'is_draft' => false
        ];
        $response = $this->actingAs($user)->post('/admin/posts', $test_post);
        $response->assertSessionHas('status', 'Post has been created sucessfully');

        $test_post_update = [
            'title' => 'test post update',
            'body' => 'This is a test functional post update',
            'is_draft' => true
        ];
        // Como usamos el RefreshDatabase elimina los datos en cada test así que es el 1 si o si
        $response = $this->actingAs($user)->put('admin/posts/1', $test_post_update);
        $response->assertSessionHas('status', 'Post has been updated sucessfully');

        $ddbb_post = Post::find(1);

        $this->assertEquals($ddbb_post['title'], $test_post_update['title']);
        $this->assertEquals($ddbb_post['body'], $test_post_update['body']);
        $this->assertEquals($ddbb_post['is_draft'], $test_post_update['is_draft']);
    }

    /**
     * Testing create posts and checked if exists and can be visualed
     *
     * @return void
     */
    public function testDeletePost()
    {
        $user = User::factory()->create();
        $test_post = [
            'title' => 'test post delete',
            'body' => 'This is a test functional post',
            'is_draft' => false
        ];
        $response = $this->actingAs($user)->post('/admin/posts', $test_post);
        $response->assertSessionHas('status', 'Post has been created sucessfully');

        $post = Post::where('title', $test_post['title'])->first();

        $response = $this->actingAs($user)->delete('admin/posts/' . $post->id);
        $response->assertSessionHas('status', 'Post has been deleted sucessfully');

        $ddbb_post = Post::find($post->id);

        $this->assertNull($ddbb_post);
    }

    public function testEditPostStaffMember()
    {
        $user_admin = User::factory()->create();
        $test_post = [
            'title' => 'test post admin',
            'body' => 'This is a test functional post',
            'is_draft' => false
        ];
        $response = $this->actingAs($user_admin)->post('/admin/posts', $test_post);
        $response->assertSessionHas('status', 'Post has been created sucessfully');

        $post = Post::where('title', $test_post['title'])->first();

        $user_staff = $this->createStaffUser();

        $test_post_update = [
            'title' => 'test post update',
            'body' => 'This is a test functional post update',
            'is_draft' => true
        ];
        $response = $this->actingAs($user_staff)->put('admin/posts/' . $post->id, $test_post_update);
        $response->assertStatus(401);

        $ddbb_post = Post::find($post->id);

        $this->assertEquals($ddbb_post['title'], $test_post['title']);
        $this->assertEquals($ddbb_post['body'], $test_post['body']);
        $this->assertEquals($ddbb_post['is_draft'], $test_post['is_draft']);
    }

    public function testDeletePostStaffMember()
    {
        $user_admin = User::factory()->create();
        $test_post = [
            'title' => 'test post delete admin',
            'body' => 'This is a test functional post',
            'is_draft' => false
        ];
        $response = $this->actingAs($user_admin)->post('/admin/posts', $test_post);
        $response->assertSessionHas('status', 'Post has been created sucessfully');

        $post = Post::where('title', $test_post['title'])->first();

        $user_staff = $this->createStaffUser();

        $post = Post::where('title', $test_post['title'])->first();

        $response = $this->actingAs($user_staff)->delete('admin/posts/' . $post->id);

        $ddbb_post = Post::find($post->id);

        $this->assertNotNull($ddbb_post);
        $response->assertStatus(401);
    }

    private function createStaffUser()
    {
        $user_staff = new User;
        $user_staff->name = rand(1, 99) . 'Staff user';
        $user_staff->email = rand(1, 99) . 'staff@cosasdedevs.com';
        $user_staff->password = bcrypt('123456');
        $user_staff->is_staff = true;
        $user_staff->save();
        return $user_staff;
    }
}

Y con esto podemos dar por cerrado esta serie de tutoriales 🥱. Espero que os ayude en vuestros inicios con Laravel 8. También os recomiendo que hagáis vuestros propios proyectos y que consultéis la documentación con regularidad, lo bueno de Laravel es que tiene una documentación de muy buena calidad, a mí me ha salvado de volverme loco más de una vez 😅.

Como siempre os dejo el enlace al proyecto.

Espero que este post te ayude y como siempre, te recomiendo seguirme en Twitter para estar al tanto de los nuevo contenido. Ahora también puedes seguirme en Instagram donde estoy subiendo tips, tutoriales en vídeo e información sobre herramientas para developers.

Por último os dejo mi guía para aprender a trabajar con APIs donde explico todo el funcionamiento de una API, el protocolo HTTP y veremos como construir una API con arquitectura REST.

Nos leemos 👋.

14908 vistas

🐍 Sígueme en Twitter

Si te gusta el contenido que subo y no quieres perderte nada, sígueme en Twitter y te avisaré cada vez que cree contenido nuevo 💪
Luego ¡Te sigo!

Nos tomamos en serio tu privacidad

Utilizamos cookies propias y de terceros para recopilar y analizar datos sobre la interacción de los usuarios con cosasdedevs.com. Ver política de cookies.