Passer au contenu principal

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

Utilisez get, 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('/');
Précisez la garde en 2ᵉ argument.
$this->actingAs($user, 'web');
Déconnecter :
$this->actingAsGuest();

Débogage de la réponse

$response->dump();
$response->dumpHeaders();
$response->dumpSession();
Pour arrêter l’exécution :
$response->dd();
$response->ddHeaders();
$response->ddBody();
$response->ddJson();
$response->ddSession();

Tests d’exceptions

Utilisez la façade Exceptions.
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();
Désactiver la gestion des exceptions :
$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,
    ]);
Accès aux données comme à un tableau :
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');
Avec closure :
$response->assertJsonPath('team.owner.name', fn (string $name) => strlen($name) >= 3);

Fluent JSON

Utilisez AssertableJson.
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('[email protected]'))
            ->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'
        ])
);
Union de types via | :
$response->assertJson(fn (AssertableJson $json) =>
    $json->whereType('name', 'string|null')
        ->whereType('id', ['string', 'integer'])
);
Types : 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');
});
Garde précise : $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'                 => '[email protected]',
        'password'              => 'password',
        'password_confirmation' => 'password',
    ]);

    $response->assertRedirect('/dashboard');
    $this->assertDatabaseHas('users', ['email' => '[email protected]']);
});

test('email dupliqué', function () {
    User::factory()->create(['email' => '[email protected]']);

    $response = $this->post('/register', [
        'name'                  => 'Test User',
        'email'                 => '[email protected]',
        '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éthodeDescription
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éthodeDescription
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('[email protected]');
});

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éthodeUsage
$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
AssertionVé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
FakeRô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
Dernière modification le 13 juillet 2026