Passer au contenu principal

Pourquoi Pest est devenu la valeur par défaut de Laravel

À partir de Laravel 11, quand vous créez un projet avec laravel new, Pest est sélectionné comme framework de test par défaut. PHPUnit est utilisé depuis longtemps comme outil de test standard en PHP, mais Pest s’appuie sur PHPUnit tout en offrant une écriture de tests plus concise et lisible.
Pest s’exécute au-dessus de PHPUnit, si bien que les tests PHPUnit existants tournent tels quels. La migration peut se faire progressivement.
Derrière l’adoption officielle de Pest se cache un objectif : réduire les frictions d’écriture des tests. En supprimant le rituel « définir une classe, écrire une méthode », en fournissant une écriture qui met l’accent sur « ce qu’on veut tester », on incite naturellement à en écrire davantage.

Principales différences avec PHPUnit

Syntaxe des tests

La différence la plus évidente est la manière d’écrire un test.
test('un utilisateur peut se connecter', function () {
    $user = User::factory()->create();

    $response = $this->post('/login', [
        'email' => $user->email,
        'password' => 'password',
    ]);

    $response->assertRedirect('/dashboard');
});
La fonction test() de Pest accepte une closure. Aucune classe ni méthode à définir : l’intention est claire dès la première ligne. it() s’utilise de la même manière — it('can login', ...) se lit comme une phrase anglaise.

API expect()

Le point le plus emblématique de Pest est l’assertion en chaînage via expect().
test('l’API de liste des articles renvoie du JSON', function () {
    $posts = Post::factory(3)->create();

    $response = $this->getJson('/api/posts');

    expect($response->status())->toBe(200);
    expect($response->json('data'))->toHaveCount(3);
    expect($response->json('data.0.title'))->not->toBeEmpty();
});
expect($value)->toBe(), ->toBeNull(), ->toContain(), ->toHaveCount() — les assertions se lisent naturellement. Vous pouvez chaîner plusieurs assertions.
expect($user)
    ->name->toBe('Tarō Tanaka')
    ->email->toBe('[email protected]')
    ->is_admin->toBeFalse();

Setup et teardown

L’équivalent de setUp() en PHPUnit est beforeEach() ; celui de tearDown() est afterEach().
beforeEach(function () {
    $this->user = User::factory()->create();
    $this->actingAs($this->user);
});

test('un utilisateur authentifié peut accéder à son profil', function () {
    $response = $this->get('/profile');
    $response->assertOk();
});

test('un utilisateur authentifié peut mettre à jour son profil', function () {
    $response = $this->patch('/profile', ['name' => 'Nouveau nom']);
    $response->assertRedirect('/profile');
});

Datasets — tests table-driven

Pour rejouer la même logique de test avec plusieurs jeux de données, utilisez dataset.
test('une adresse e-mail invalide provoque une erreur de validation', function (string $email) {
    $response = $this->postJson('/api/users', [
        'name' => 'Tarō Tanaka',
        'email' => $email,
    ]);

    $response->assertUnprocessable();
})->with([
    'email absent' => [''],
    'format invalide' => ['notanemail'],
    'double @' => ['a@@example.com'],
]);
Chaque libellé de dataset ('email absent', etc.) est ajouté au nom du test, permettant de voir en un coup d’œil quel cas a échoué.

Tests arch() — vérification automatique de l’architecture

arch() de Pest vérifie la structure du code. Vous pouvez faire respecter automatiquement des règles telles que « les contrôleurs ne dépendent pas directement des modèles » ou « les modèles étendent Eloquent ».
test('les modèles étendent Eloquent', function () {
    arch()->expect('App\Models')
        ->toExtend(Illuminate\Database\Eloquent\Model::class);
});

test('les contrôleurs sont déclarés final', function () {
    arch()->expect('App\Http\Controllers')
        ->toBeFinal();
});

test('les services ne dépendent pas des contrôleurs', function () {
    arch()->expect('App\Services')
        ->not->toUse('App\Http\Controllers');
});
Les tests arch() analysent statiquement le code : ils n’envoient aucune requête HTTP et n’accèdent pas à la base. Ils sont donc extrêmement rapides.
Pest fournit également des préréglages de règles d’architecture courantes.
test('respecte les règles d’architecture Laravel', function () {
    arch()->preset()->laravel();
});
Le preset laravel() regroupe les règles alignées sur les conventions Laravel : nommage des modèles, héritage des contrôleurs, structure des middlewares, etc.

Exemple concret dans un projet Laravel

Créer un test

php artisan make:test UserTest
Depuis Laravel 11, avec la configuration par défaut, un fichier de test au format Pest est généré.
<?php

test('example', function () {
    expect(true)->toBeTrue();
});

Utiliser directement les helpers de test Laravel

Pest étend le TestCase Laravel : actingAs(), assertDatabaseHas(), les helpers HTTP, etc. sont tous disponibles.
use App\Models\Post;
use App\Models\User;

test('un utilisateur peut supprimer son propre article', function () {
    $user = User::factory()->create();
    $post = Post::factory()->for($user)->create();

    $response = $this
        ->actingAs($user)
        ->delete("/posts/{$post->id}");

    $response->assertRedirect('/posts');
    $this->assertModelMissing($post);
});

test('un utilisateur ne peut pas supprimer l’article d’un autre', function () {
    $user = User::factory()->create();
    $otherUser = User::factory()->create();
    $post = Post::factory()->for($otherUser)->create();

    $this
        ->actingAs($user)
        ->delete("/posts/{$post->id}")
        ->assertForbidden();

    $this->assertModelExists($post);
});

Factories et transactions de base

Les traits RefreshDatabase et DatabaseTransactions s’appliquent facilement avec uses(). Écrit en tête de fichier, cela s’applique au fichier entier.
uses(Tests\TestCase::class, Illuminate\Foundation\Testing\RefreshDatabase::class)->in('Feature');
Configuré une fois dans tests/Pest.php, RefreshDatabase est activé automatiquement pour tous les tests Feature. Chaque fichier peut le surcharger si nécessaire.

Cohabitation avec les tests PHPUnit existants

Pest et PHPUnit cohabitent au sein d’un même projet. Il n’y a rien à réécrire ; il suffit d’écrire les nouveaux tests en style Pest.
./vendor/bin/pest       # Exécute tous les tests avec Pest (y compris les PHPUnit)
./vendor/bin/pest --filter="utilisateur"  # Filtrer par nom de test
php artisan test        # La commande Artisan utilise également Pest
Depuis Laravel 11, si Pest est installé, php artisan test l’utilise automatiquement.

Étapes de migration

  1. Ajouter la configuration uses() dans tests/Pest.php
  2. Écrire les nouveaux tests en style Pest
  3. Migrer progressivement les tests PHPUnit existants en vérifiant qu’ils passent
Pas besoin de tout réécrire d’un coup. Le coût d’apprentissage de la syntaxe Pest étant faible par rapport à PHPUnit, l’adoption par l’équipe se fait en douceur.

Conclusion

CritèrePHPUnitPest
Définition d’un testClasse + méthodeFonction test() / it()
Assertions$this->assert*()expect()->to*()
SetupsetUp()beforeEach()
Data-driven@dataProvider->with()
Vérification d’architectureAucunearch()
Vitesse d’exécutionStandardÉquivalente (basé sur PHPUnit)
Pest n’est pas un remplaçant de PHPUnit : c’est une couche supérieure qui l’enveloppe. Son intégration à Laravel est profonde, au point d’être choisi par défaut à la génération d’un starter kit. Le coût d’introduction dans un projet existant est faible ; commencer par les nouveaux tests suffit à récolter les bénéfices. Écrire plus de tests, c’est trouver plus tôt les bugs et abaisser la barrière psychologique du refactor. Pest est un outil qui vise à réduire précisément « la friction de l’acte d’écrire un test ».

Documentation officielle Pest

Datasets, couverture, exécution parallèle, etc. : consultez la documentation officielle pour toutes les fonctionnalités de Pest.
Dernière modification le 13 juillet 2026