Tester avec Laravel

Voir la vidéo
Description Sommaire

Dans ce nouveau chapitre, je vous propose de parler des tests et on va voir comment Laravel nous permet de tester une application. Comme d'habitude, Laravel a pensé les choses en amont et intègre déjà les outils nécessaires aux tests. La première chose qu'on peut remarquer, c'est que par défaut, on a déjà un fichier phpunit.xml qui permet de définir la configuration pour lancer les tests via l'outil phpunit. De la même manière, on a la possibilité avec la commande artisan de lancer les tests.

php artisan test

A la racine du projet on peut noter la présence d'un dossier tests qui va contenir deux sous-dossiers.

  • Feature va contenir les tests fonctionnels. Ce sont des tests dans lesquels vous aurez accès à l'environnement de l'application Laravel (Conteneur de service, Facades...) et qui vous permettront de tester vos classes en lien avec le reste de l'application.
  • Unit va contenir des tests unitaires où on va tester notre code en total isolation. Ces tests vont utiliser la classe TestCase de base de PHPUnit.

Tests fonctionnels

Pour tester le fonctionnement général de notre application on va pouvoir simuler une requête et faire des assertions sur la réponse obtenue.

<?php

namespace Tests\Feature;

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

class HomeTest extends TestCase
{
    public function test_home_page_respond_correctly(): void
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}

Différentes assertions permettent de vérifier que le comportement attendu est le bon. Par exemple dans le cadre de l'envoi d'un formulaire on peut vérifier que les erreurs sont bien en sessions.

<?php

namespace Tests\Feature;

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

class ContactTest extends TestCase
{
    public function test_contact_respond_correctly(): void
    {
        $response = $this->post('/contact', [
            'name' => 'John Doe',
            'email' => 'fake',
            'content' => 'This is a fake content'
        ]);

        $response->assertRedirect();
        $response->assertSessionHasErrors(['email']);
        $response->assertSessionHasInput('email', 'fake');
    }
}

Tester avec la base de données

Pour initialiser la base de données pour un test il est possible d'utiliser le trait RefreshDatabase

use Illuminate\Foundation\Testing\RefreshDatabase;

class ExampleTest extends TestCase
{

    use RefreshDatabase;

    public function test_with_db(): void
    {
        $response = $this->get('/');
        $response->assertOk();
    }
}

Vous pourrez ensuite utiliser les factory pour remplir votre base de données avec les données nécessaire au test ou vous pouvez utiliser la méthode seed() pour remplir la base de données à l'aide des seeders que l'on a vu dans un chapitre précédent.

$this->seed(OrderStatusSeeder::class);

Enfin, si votre objectif est de vérifier que l'action sauvegarde des enregistrements en base de données vous pouvez utiliser l'assertion assertDatabaseHas.

$this->assertDatabaseCount('users', 5);
$this->assertDatabaseHas('users', [
    'email' => 'sally@example.com',
]);

N'hésitez pas à faire un tour sur la documentation pour découvrir les assertions disponibles.

Tester avec l'Authentification

Aussi, si vous action ont un accès limité à certains utilisateurs vous pouvez utiliser la méthode actingAs() et withSession() pour remplir la session.

<?php

namespace Tests\Feature;

use App\Models\User;
use Tests\TestCase;

class DashboardTest extends TestCase
{
    public function test_dashboard(): void
    {
        $user = User::factory()->admin()->create();

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

Mocker un service

Lorsque votre code interagit avec des éléments tiers (comme une API par exemple) il est nécessaire de pouvoir simuler les différents type de retour. Laravel intègre Mockery qui permet de changer à la volée le comportement d'une classe.

use App\Service\WeatherApi;
use Mockery\MockInterface;

$mock = $this->mock(WeatherApi::class, function (MockInterface $mock) {
    $mock->shouldReceive('isSunny')->once()->andReturn(false);
});

Dans le cas des façade, il est possible de les mocker directement sur la Facade. Ce mocking fonctionne aussi dans le cadre de test unitaire mais il faudra dans ce cas là penser à réinitialiser dans la fonction tearDown().

<?php

namespace Tests\Feature;

use Illuminate\Support\Facades\Cache;
use Tests\TestCase;

class UserControllerTest extends TestCase
{
    public function test_get_index(): void
    {
        Cache::shouldReceive('get')
                    ->once()
                    ->with('key')
                    ->andReturn('value');

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

Tests unitaires

Les tests unitaires fonctionnent de manière classique mais on notera quelques subtilités.

Mockery pour les mocks

Laravel intègre Mockery pour la gestion des mocks.

public function test_weather(): void
{
    $mock = \Mockery::mock(WeatherApiClient::class);
    $mock->shouldReceive('isSunny')->once()->andReturn(false);
    $service = new WeatherService($mock);
    // ...
}

Si vos fonctions utilisent les façades vous pourrez aussi les mocker en utilisant la méthode que l'on a vu précédemment. Par contre il faudra penser à les réinitialiser après chaque test.

protected function tearDown(): void
{
    parent::tearDown();
    Cache::clearResolvedInstances();
}
Publié
Technologies utilisées
Auteur :
Grafikart
Partager