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 classeTestCase
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();
}