> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Tests HTTP

> Simuler des requêtes HTTP, faire des assertions, tester l'authentification et les uploads de fichiers avec Laravel.

## Introduction

Laravel fournit une API riche pour simuler des requêtes HTTP et vérifier les réponses, sans démarrer de vrai serveur.

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('l\'application retourne une réponse OK', function () {
      $response = $this->get('/');

      $response->assertStatus(200);
  });
  ```

  ```php PHPUnit theme={null}
  <?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);
      }
  }
  ```
</CodeGroup>

`get()` envoie une requête GET simulée ; `assertStatus()` vérifie le code HTTP.

<Info>
  Le middleware CSRF est automatiquement désactivé pendant les tests.
</Info>

## 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`.

<CodeGroup>
  ```php Pest theme={null}
  <?php

  test('requête de base', function () {
      $response = $this->get('/');

      $response->assertStatus(200);
  });
  ```

  ```php PHPUnit theme={null}
  <?php

  namespace Tests\Feature;

  use Tests\TestCase;

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

          $response->assertStatus(200);
      }
  }
  ```
</CodeGroup>

<Warning>
  Une seule requête par test recommandé. Plusieurs peuvent produire des comportements inattendus.
</Warning>

### Personnaliser les en-têtes

```php theme={null}
$response = $this->withHeaders([
    'X-Header' => 'Value',
])->post('/user', ['name' => 'Sally']);
```

### Cookies

```php theme={null}
$response = $this->withCookie('color', 'blue')->get('/');

$response = $this->withCookies([
    'color' => 'blue',
    'name'  => 'Taylor',
])->get('/');
```

### Session et authentification

```php theme={null}
$response = $this->withSession(['banned' => false])->get('/');
```

`actingAs()` connecte un utilisateur.

```php theme={null}
use App\Models\User;

$user = User::factory()->create();

$response = $this->actingAs($user)
    ->withSession(['banned' => false])
    ->get('/');
```

Précisez la garde en 2ᵉ argument.

```php theme={null}
$this->actingAs($user, 'web');
```

Déconnecter :

```php theme={null}
$this->actingAsGuest();
```

### Débogage de la réponse

```php theme={null}
$response->dump();
$response->dumpHeaders();
$response->dumpSession();
```

Pour arrêter l'exécution :

```php theme={null}
$response->dd();
$response->ddHeaders();
$response->ddBody();
$response->ddJson();
$response->ddSession();
```

### Tests d'exceptions

Utilisez la façade `Exceptions`.

```php theme={null}
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.

```php theme={null}
Exceptions::assertNotReported(InvalidOrderException::class);

Exceptions::assertNothingReported();
```

Désactiver la gestion des exceptions :

```php theme={null}
$response = $this->withoutExceptionHandling()->get('/');
```

`assertThrows()` :

```php theme={null}
$this->assertThrows(
    fn () => (new ProcessOrder)->execute(),
    OrderInvalid::class
);

$this->assertThrows(
    fn () => (new ProcessOrder)->execute(),
    fn (OrderInvalid $e) => $e->orderId() === 123
);
```

`assertDoesntThrow()` :

```php theme={null}
$this->assertDoesntThrow(fn () => (new ProcessOrder)->execute());
```

## Tests JSON

`json`, `getJson`, `postJson`, `putJson`, `patchJson`, `deleteJson`, `optionsJson`.

```php theme={null}
$response = $this->postJson('/api/user', ['name' => 'Sally']);

$response
    ->assertStatus(201)
    ->assertJson([
        'created' => true,
    ]);
```

Accès aux données comme à un tableau :

```php theme={null}
expect($response['created'])->toBeTrue();
// ou en PHPUnit :
$this->assertTrue($response['created']);
```

<Info>
  `assertJson()` vérifie qu'un fragment JSON est présent (autres propriétés autorisées).
</Info>

### Correspondance exacte

`assertExactJson()` exige une égalité stricte.

```php theme={null}
$response = $this->postJson('/user', ['name' => 'Sally']);

$response
    ->assertStatus(201)
    ->assertExactJson([
        'created' => true,
    ]);
```

### JSON path

```php theme={null}
$response = $this->postJson('/user', ['name' => 'Sally']);

$response
    ->assertStatus(201)
    ->assertJsonPath('team.owner.name', 'Darian');
```

Avec closure :

```php theme={null}
$response->assertJsonPath('team.owner.name', fn (string $name) => strlen($name) >= 3);
```

### Fluent JSON

Utilisez `AssertableJson`.

```php theme={null}
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()
    );
```

<Tip>
  `etc()` autorise des propriétés supplémentaires. Sans `etc()`, une propriété inattendue fait échouer le test — cela évite des fuites accidentelles.
</Tip>

`has()` / `missing()` :

```php theme={null}
$response->assertJson(fn (AssertableJson $json) =>
    $json->has('data')
        ->missing('message')
);
```

`hasAll()` / `missingAll()` :

```php theme={null}
$response->assertJson(fn (AssertableJson $json) =>
    $json->hasAll(['status', 'data'])
        ->missingAll(['message', 'code'])
);
```

#### Collections JSON

```php theme={null}
$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 :

```php theme={null}
$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

```php theme={null}
$response->assertJson(fn (AssertableJson $json) =>
    $json->whereType('id', 'integer')
        ->whereAllType([
            'users.0.name' => 'string',
            'meta' => 'array'
        ])
);
```

Union de types via `|` :

```php theme={null}
$response->assertJson(fn (AssertableJson $json) =>
    $json->whereType('name', 'string|null')
        ->whereType('id', ['string', 'integer'])
);
```

Types : `string`, `integer`, `double`, `boolean`, `array`, `null`.

## Tests d'authentification

```php theme={null}
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

```php theme={null}
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

```php theme={null}
$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()`.

```php theme={null}
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

```php theme={null}
Storage::disk('avatars')->assertExists('avatar.jpg');
Storage::disk('avatars')->assertMissing('other.jpg');
Storage::disk('avatars')->assertCount(1);
Storage::disk('avatars')->assertEmpty();
```

### Fake images / fichiers

```php theme={null}
$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

```php theme={null}
$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.

```php theme={null}
$response->assertValid();

$response->assertValid('email');

$response->assertValid(['name', 'email']);
```

## Middleware bypass

Ignorer temporairement des middlewares.

```php theme={null}
$response = $this->withoutMiddleware()->get('/dashboard');

// Précis
$response = $this->withoutMiddleware([Authenticate::class])->get('/dashboard');
```

<Warning>
  Utile pour se concentrer sur la logique du contrôleur, mais préférez tester avec les middlewares réels quand c'est possible.
</Warning>

## Événements, jobs, mails, notifications

### `Event::fake()`

```php theme={null}
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()`

```php theme={null}
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()`

```php theme={null}
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()`

```php theme={null}
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

```php theme={null}
$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 :

```php theme={null}
$view = $this->blade(
    '<x-alert type="error">{{ $slot }}</x-alert>',
    ['slot' => 'Error message']
);

$view->assertSee('Error message');
```

## Récapitulatif

<AccordionGroup>
  <Accordion title="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 |
  </Accordion>

  <Accordion title="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          |
  </Accordion>

  <Accordion title="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             |
  </Accordion>
</AccordionGroup>


## Related topics

- [Introduction aux tests](/fr/testing.md)
- [Client HTTP](/fr/http-client.md)
- [Tests avancés avec Pest](/fr/advanced/testing-pest.md)
- [Guide de développement d'applications intégrées à l'API du moteur - VOICEVOX for Laravel](/fr/packages/laravel-voicevox/app-guide.md)
- [Tests](/fr/packages/laravel-bluesky/testing.md)
