Introduction
Laravel fournit une API riche pour simuler des requêtes HTTP et vérifier les réponses, sans démarrer de vrai serveur.<?php
test('l\'application retourne une réponse OK', function () {
$response = $this->get('/');
$response->assertStatus(200);
});
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_the_application_returns_a_successful_response(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
get() envoie une requête GET simulée ; assertStatus() vérifie le code HTTP.
Le middleware CSRF est automatiquement désactivé pendant les tests.
Créer des requêtes
Utilisezget, post, put, patch, delete dans vos tests. Aucune vraie requête réseau ; le retour est une Illuminate\Testing\TestResponse.
<?php
test('requête de base', function () {
$response = $this->get('/');
$response->assertStatus(200);
});
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_a_basic_request(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
Une seule requête par test recommandé. Plusieurs peuvent produire des comportements inattendus.
Personnaliser les en-têtes
$response = $this->withHeaders([
'X-Header' => 'Value',
])->post('/user', ['name' => 'Sally']);
Cookies
$response = $this->withCookie('color', 'blue')->get('/');
$response = $this->withCookies([
'color' => 'blue',
'name' => 'Taylor',
])->get('/');
Session et authentification
$response = $this->withSession(['banned' => false])->get('/');
actingAs() connecte un utilisateur.
use App\Models\User;
$user = User::factory()->create();
$response = $this->actingAs($user)
->withSession(['banned' => false])
->get('/');
$this->actingAs($user, 'web');
$this->actingAsGuest();
Débogage de la réponse
$response->dump();
$response->dumpHeaders();
$response->dumpSession();
$response->dd();
$response->ddHeaders();
$response->ddBody();
$response->ddJson();
$response->ddSession();
Tests d’exceptions
Utilisez la façadeExceptions.
use App\Exceptions\InvalidOrderException;
use Illuminate\Support\Facades\Exceptions;
Exceptions::fake();
$response = $this->get('/order/1');
Exceptions::assertReported(InvalidOrderException::class);
Exceptions::assertReported(function (InvalidOrderException $e) {
return $e->getMessage() === 'The order was invalid.';
});
assertNotReported / assertNothingReported pour l’inverse.
Exceptions::assertNotReported(InvalidOrderException::class);
Exceptions::assertNothingReported();
$response = $this->withoutExceptionHandling()->get('/');
assertThrows() :
$this->assertThrows(
fn () => (new ProcessOrder)->execute(),
OrderInvalid::class
);
$this->assertThrows(
fn () => (new ProcessOrder)->execute(),
fn (OrderInvalid $e) => $e->orderId() === 123
);
assertDoesntThrow() :
$this->assertDoesntThrow(fn () => (new ProcessOrder)->execute());
Tests JSON
json, getJson, postJson, putJson, patchJson, deleteJson, optionsJson.
$response = $this->postJson('/api/user', ['name' => 'Sally']);
$response
->assertStatus(201)
->assertJson([
'created' => true,
]);
expect($response['created'])->toBeTrue();
// ou en PHPUnit :
$this->assertTrue($response['created']);
assertJson() vérifie qu’un fragment JSON est présent (autres propriétés autorisées).Correspondance exacte
assertExactJson() exige une égalité stricte.
$response = $this->postJson('/user', ['name' => 'Sally']);
$response
->assertStatus(201)
->assertExactJson([
'created' => true,
]);
JSON path
$response = $this->postJson('/user', ['name' => 'Sally']);
$response
->assertStatus(201)
->assertJsonPath('team.owner.name', 'Darian');
$response->assertJsonPath('team.owner.name', fn (string $name) => strlen($name) >= 3);
Fluent JSON
UtilisezAssertableJson.
use Illuminate\Testing\Fluent\AssertableJson;
$response = $this->getJson('/users/1');
$response
->assertJson(fn (AssertableJson $json) =>
$json->where('id', 1)
->where('name', 'Victoria Faith')
->where('email', fn (string $email) => str($email)->is('victoria@gmail.com'))
->whereNot('status', 'pending')
->missing('password')
->etc()
);
etc() autorise des propriétés supplémentaires. Sans etc(), une propriété inattendue fait échouer le test — cela évite des fuites accidentelles.has() / missing() :
$response->assertJson(fn (AssertableJson $json) =>
$json->has('data')
->missing('message')
);
hasAll() / missingAll() :
$response->assertJson(fn (AssertableJson $json) =>
$json->hasAll(['status', 'data'])
->missingAll(['message', 'code'])
);
Collections JSON
$response
->assertJson(fn (AssertableJson $json) =>
$json->has(3)
->first(fn (AssertableJson $json) =>
$json->where('id', 1)
->where('name', 'Victoria Faith')
->missing('password')
->etc()
)
);
each() pour vérifier tous les éléments :
$response
->assertJson(fn (AssertableJson $json) =>
$json->has(3)
->each(fn (AssertableJson $json) =>
$json->whereType('id', 'integer')
->whereType('name', 'string')
->whereType('email', 'string')
->missing('password')
->etc()
)
);
Assertions de type
$response->assertJson(fn (AssertableJson $json) =>
$json->whereType('id', 'integer')
->whereAllType([
'users.0.name' => 'string',
'meta' => 'array'
])
);
| :
$response->assertJson(fn (AssertableJson $json) =>
$json->whereType('name', 'string|null')
->whereType('id', ['string', 'integer'])
);
string, integer, double, boolean, array, null.
Tests d’authentification
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('les utilisateurs authentifiés voient le dashboard', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)->get('/dashboard');
$response->assertStatus(200);
});
test('les non authentifiés sont redirigés', function () {
$response = $this->get('/dashboard');
$response->assertRedirect('/login');
});
$this->actingAs($user, 'api');.
Exemple : inscription
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
test('inscription réussie', function () {
$response = $this->post('/register', [
'name' => 'Test User',
'email' => 'test@example.com',
'password' => 'password',
'password_confirmation' => 'password',
]);
$response->assertRedirect('/dashboard');
$this->assertDatabaseHas('users', ['email' => 'test@example.com']);
});
test('email dupliqué', function () {
User::factory()->create(['email' => 'test@example.com']);
$response = $this->post('/register', [
'name' => 'Test User',
'email' => 'test@example.com',
'password' => 'password',
'password_confirmation' => 'password',
]);
$response->assertSessionHasErrors('email');
});
Tests de session
$response = $this->withSession(['locale' => 'fr'])->get('/');
$response->assertSessionHas('locale', 'fr');
Assertions de session
| Méthode | Description |
|---|---|
assertSessionHas($key, $value) | La clé existe avec la valeur |
assertSessionHasAll([...]) | Plusieurs clés/valeurs |
assertSessionHasErrors($keys) | Erreurs de validation présentes |
assertSessionHasErrorsIn($bag, $keys) | Erreurs dans une error bag précise |
assertSessionHasNoErrors() | Pas d’erreurs |
assertSessionDoesntHaveErrors() | Idem |
assertSessionMissing($key) | Absente |
Upload de fichiers
UploadedFile::fake() + Storage::fake().
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
test('upload d\'avatar', function () {
Storage::fake('avatars');
$file = UploadedFile::fake()->image('avatar.jpg');
$response = $this->post('/avatar', [
'avatar' => $file,
]);
Storage::disk('avatars')->assertExists($file->hashName());
});
Assertions Storage
Storage::disk('avatars')->assertExists('avatar.jpg');
Storage::disk('avatars')->assertMissing('other.jpg');
Storage::disk('avatars')->assertCount(1);
Storage::disk('avatars')->assertEmpty();
Fake images / fichiers
$file = UploadedFile::fake()->image('avatar.jpg');
$file = UploadedFile::fake()->image('avatar.jpg', 640, 480);
$file = UploadedFile::fake()->create('document.pdf', 1024, 'application/pdf');
Assertions de réponse
| Méthode | Description |
|---|---|
assertStatus($code) | Code HTTP |
assertOk() | 200 |
assertCreated() | 201 |
assertNoContent() | 204 |
assertNotFound() | 404 |
assertUnauthorized() | 401 |
assertForbidden() | 403 |
assertUnprocessable() | 422 |
assertRedirect($uri) | Redirection |
assertRedirectToRoute($name) | Vers route nommée |
assertSee($value) | Chaîne dans la réponse |
assertDontSee($value) | Absente |
assertSeeText($value) | Chaîne présente (texte échappé) |
assertViewIs($name) | Vue rendue |
assertViewHas($key, $value) | Donnée passée à la vue |
assertViewMissing($key) | Absente |
assertJson([...]) | Fragment JSON |
assertExactJson([...]) | JSON exact |
assertJsonCount($count) | Nombre d’éléments |
assertJsonFragment([...]) | Fragment dans un tableau |
assertJsonMissing([...]) | Fragment absent |
assertJsonStructure([...]) | Structure |
assertJsonValidationErrors($keys) | Erreurs de validation JSON |
assertHeader($key, $value) | En-tête |
assertHeaderMissing($key) | En-tête absent |
assertCookie($name, $value) | Cookie |
assertCookieExpired($name) | Cookie expiré |
assertPlainCookie($name, $value) | Cookie non chiffré |
assertSessionHas($key, $value) | Session |
assertSessionHasErrors($keys) | Erreurs en session |
assertDatabaseHas($table, [...]) | Enregistrement en base |
assertDatabaseMissing($table, [...]) | Absent en base |
Assertions de validation
$response = $this->post('/user', []);
$response->assertInvalid(['email' => 'required']);
// Message précis
$response->assertInvalid(['email' => 'The email field is required.']);
// Une seule clé
$response->assertInvalid('email');
// Plusieurs clés
$response->assertInvalid(['name', 'email']);
assertValid() inverse.
$response->assertValid();
$response->assertValid('email');
$response->assertValid(['name', 'email']);
Middleware bypass
Ignorer temporairement des middlewares.$response = $this->withoutMiddleware()->get('/dashboard');
// Précis
$response = $this->withoutMiddleware([Authenticate::class])->get('/dashboard');
Utile pour se concentrer sur la logique du contrôleur, mais préférez tester avec les middlewares réels quand c’est possible.
Événements, jobs, mails, notifications
Event::fake()
use App\Events\OrderShipped;
use Illuminate\Support\Facades\Event;
Event::fake();
// Action déclenchant l'événement...
Event::assertDispatched(OrderShipped::class);
Event::assertDispatched(OrderShipped::class, function ($event) use ($order) {
return $event->order->id === $order->id;
});
Event::assertNotDispatched(OrderShipped::class);
Event::assertDispatchedTimes(OrderShipped::class, 3);
Queue::fake()
use App\Jobs\ShipOrder;
use Illuminate\Support\Facades\Queue;
Queue::fake();
ShipOrder::dispatch($order);
Queue::assertPushed(ShipOrder::class);
Queue::assertPushed(ShipOrder::class, function ($job) use ($order) {
return $job->order->id === $order->id;
});
Queue::assertNotPushed(ShipOrder::class);
Queue::assertPushedOn('processing', ShipOrder::class);
Queue::assertNothingPushed();
Mail::fake()
use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;
Mail::fake();
// Envoi...
Mail::assertSent(OrderShipped::class);
Mail::assertSent(OrderShipped::class, function ($mail) use ($order) {
return $mail->order->id === $order->id;
});
Mail::assertSent(OrderShipped::class, function ($mail) {
return $mail->hasTo('user@example.com');
});
Mail::assertNotSent(OrderShipped::class);
Mail::assertNothingSent();
Mail::assertSentTimes(OrderShipped::class, 3);
Notification::fake()
use App\Notifications\OrderShipped;
use Illuminate\Support\Facades\Notification;
Notification::fake();
// Envoi...
Notification::assertSentTo($user, OrderShipped::class);
Notification::assertSentTo($users, OrderShipped::class);
Notification::assertNotSentTo($user, OrderShipped::class);
Notification::assertNothingSent();
Assertions de vue
$response = $this->get('/user/1');
$response->assertViewIs('user.profile');
$response->assertViewHas('user');
$response->assertViewHas('user', function ($user) {
return $user->id === 1;
});
// Passer des données à la vue directement
$view = $this->view('welcome', ['name' => 'Taylor']);
$view->assertSee('Taylor');
blade() évalue une chaîne Blade brute :
$view = $this->blade(
'<x-alert type="error">{{ $slot }}</x-alert>',
['slot' => 'Error message']
);
$view->assertSee('Error message');
Récapitulatif
Méthodes fréquentes
Méthodes fréquentes
| Méthode | Usage |
|---|---|
$this->get($uri) | GET |
$this->post($uri, $data) | POST |
$this->put($uri, $data) | PUT |
$this->patch($uri, $data) | PATCH |
$this->delete($uri) | DELETE |
$this->getJson($uri) | GET JSON |
$this->postJson($uri, $data) | POST JSON |
$this->actingAs($user) | Authentification |
$this->withHeaders([...]) | En-têtes |
$this->withSession([...]) | Session |
$this->withCookie('key', 'value') | Cookies |
$this->withoutMiddleware() | Sans middlewares |
$this->withoutExceptionHandling() | Sans gestion d’exceptions |
Assertions courantes
Assertions courantes
| Assertion | Vérifie |
|---|---|
assertStatus($code) | Code HTTP |
assertOk() / assertCreated() / assertNotFound() | Codes usuels |
assertRedirect($uri) | Redirection |
assertSee($text) | Texte présent |
assertJson([...]) | Fragment JSON |
assertExactJson([...]) | Correspondance exacte |
assertJsonPath('team.owner.name', 'Darian') | Chemin JSON |
assertSessionHasErrors($keys) | Erreurs |
assertDatabaseHas($table, [...]) | Enregistrement présent |
assertInvalid($keys) | Validation KO |
assertValid() | Validation OK |
Fakes utiles
Fakes utiles
| Fake | Rôle |
|---|---|
Event::fake() | Empêche l’exécution des événements |
Queue::fake() | Empêche l’exécution des jobs |
Mail::fake() | Empêche l’envoi de mail |
Notification::fake() | Empêche les notifications |
Storage::fake('disk') | Filesystem fictif |
Http::fake([...]) | Requêtes HTTP externes |
Exceptions::fake() | Capture les exceptions |