<!--
  canonical-url: https://www.avonture.be/fr/blog/pest_tips
  published:     2024-09-27
  This is a static plain-Markdown mirror generated at build time.
  Visit the canonical URL above for the fully rendered page, with images and interactive components.
-->

# Écrire des tests unitaires PHP avec Pest

> Maîtrisez les tests unitaires PHP avec Pest ! Découvrez l'installation facile, la syntaxe expressive expect, les datasets, les snapshots et les puissantes fonctionnalités de tests architecturaux.

---
![Écrire des tests unitaires PHP avec Pest](/img/v2/unit_tests.webp)

**TL;DR**

Voici une référence complète sur Pest, le wrapper expressif de PHPUnit pour PHP : installation, la syntaxe `it()`/`test()`/`expect()`, assertions vs expectations, datasets inline et partagés, réutilisation de cas de test PHPUnit existants, tests architecturaux (imposer des règles comme « pas de validation dans les controllers »), snapshot testing, fonctions globales personnalisées dans `tests/Pest.php`, conversion depuis PHPUnit, et outillage VSCode (Better Pest, Pest Snippets) y compris la configuration avec un container Docker.

Si vous pensez qu'écrire des tests unitaires en PHP est amusant, restez dans la salle ; les autres, sortez s'il vous plaît. Et là tout le monde sort, y compris celui qui a posé la question.

Écrire des tests unitaires *à l'ancienne* avec PHPUnit est tellement ennuyeux que presque personne ne le fait.

Et depuis un certain temps, <https://pestphp.com/> est arrivé et a totalement changé la façon de faire.

Pest est un wrapper autour de PhpUnit donc, par exemple, chaque argument en ligne de commande supporté par PhpUnit peut être utilisé avec Pest.

*Les tests unitaires ne sont qu'une couche d'une stratégie qualité ; les autres, sur ce blog, sont [l'analyse statique avec jakzal/phpqa](/blog/php-jakzal-phpqa) et [les hooks pre-commit](/blog/git-precommit), qui tournent tous les deux avant qu'un commit puisse passer.*

> **Vous cherchez plutôt des tests navigateur/fonctionnels ?:**
>
> Cet article se concentre sur les tests unitaires classiques. Pest v4 a ajouté le testing navigateur intégré ; Pest v5 (actuel, juillet 2026) ajoute le Test Impact Analysis et requiert PHP 8.4 — voir [Writing functional tests with Pest](/blog/pest-functional-testing). Vous testez des scripts Bash ? Jetez un œil à [Lancer des tests unitaires avec bats/bats](/blog/bats-unit-tests).

## Résultat {#result}

Un fichier de test, et la ligne de bootstrap qui le relie au `TestCase` de PHPUnit :

**tests/Pest.php**

```php
uses(Tests\TestCase::class)->in('Features');
```

**tests/Feature/MyFirstTest.php**

```php
<?php

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

test('assert false is not true', function () {
    expect(false)->not->toBeTrue();   // we can also write `not()->`
});
```

Le lancer avec `./vendor/bin/pest tests/Feature/MyFirstTest.php` donne :

```bash
   PASS  Tests\Feature\MyFirstTest
  ✓ assert true is true
  ✓ assert false is not true

  Tests:  2 passed
  Time:   0.08s
```

## Pourquoi Pest {#why-pest}

* Les tests se lisent comme des phrases naturelles : `expect($value)->toBeTrue()` au lieu du `$this->assertTrue($value)` de PHPUnit — les méthodes peuvent être niées avec `not->`.
* `it()` et `test()` sont interchangeables ; utilisez celui qui correspond à votre convention de nommage, les deux partagent le même comportement et la même syntaxe.
* Pest est un wrapper autour de PHPUnit, pas un remplaçant : chaque argument en ligne de commande de PHPUnit fonctionne toujours, et les cas de test PHPUnit existants peuvent être réutilisés tels quels (il suffit d'ajouter `/** @test */`).

## Installation {#installation}

> <https://pestphp.com/docs/installation#installation>

Lancez simplement les commandes ci-dessous dans le dossier de votre application PHP :

```bash
$ composer require pestphp/pest --dev --with-all-dependencies

$ composer require pestphp/pest-plugin-laravel --dev

$ php artisan pest:install

$ ./vendor/bin/pest --init
```

À partir de maintenant, on peut lancer `./vendor/bin/pest` pour exécuter nos tests Pest.

## Écrire des tests {#writing-tests}

### Introduction à Pest {#introduction-about-pest}

#### Les fichiers doivent avoir le suffixe Test {#files-should-have-the-test-suffix}

Comme PHPUnit, Pest traitera tous les fichiers des dossiers `tests/Feature` et `tests/Unit` ayant le suffixe `Test` comme par exemple `ShoppingBasketTest.php`.

#### Que signifie $this dans un test ? {#what-does-this-mean-in-a-test}

Dans notre fichier `tests/Pest.php`, on a la ligne de bootstrap montrée en haut de cet article.

Dans un test Pest, `$this` fait référence à la classe PHPUnit `Tests\TestCase`.

#### it ou test {#it-or-test}

Pest nous laisse le choix entre `it()` et `test()`. *Utilisez celui qui correspond le mieux à votre convention de nommage des tests, ou les deux. Ils partagent le même comportement et la même syntaxe.*

Pour en savoir plus : <https://pestphp.com/docs/writing-tests#api-reference>

Le résultat est le même, seule la sortie dans la console diffère.

### Nos premiers tests {#our-first-tests}

C'est le test `MyFirstTest.php` et sa sortie `PASS` montrés en haut de cet article. Il illustre que Pest démarre avec un verbe `expect` et une méthode comme `toBeTrue()`. Les méthodes peuvent être niées avec `not->` (<https://pestphp.com/docs/expectations#expect-not>).

#### Autocomplétion {#autocomplete}

Veillez à installer et activer [PHP Intelephense](https://marketplace.visualstudio.com/items?itemName=bmewburn.vscode-intelephense-client) et profitez de l'autocomplétion de vscode.

#### Différence entre toBe et toEqual {#difference-between-tobe-and-toequal}

**tests/Feature/AnyTest.php**

```php
<?php

test('assert count is correct', function () {
    expect(2 + 2)->toBe(4);       // Will be true
    expect(2 + 2)->toBe('4');     // Will NOT be true

    expect(2 + 2)->toEqual(4);    // Will be true
    expect(2 + 2)->toEqual('4');  // Will be true
});
```

`toBe` sera plus strict, c'est-à-dire qu'il vérifiera à la fois la valeur et le type de donnée, alors que `toEqual` vérifiera uniquement la valeur.

### Assertions {#assertions}

> <https://pestphp.com/docs/assertions>

Les assertions viennent de PhpUnit et fonctionnent de la même manière.

Les assertions sont accessibles via l'objet `$this`, et ce parce que `tests/pest.php` contient la ligne ci-dessous.

**tests/pest.php**

```php
uses(Tests\TestCase::class)->in('Feature');
```

Donc `$this` fait référence à la classe PHPUnit `Tests\TestCase`.

### Expectations {#expectations}

> <https://pestphp.com/docs/expectations>

En plus des assertions, Pest vous propose un ensemble d'expectations. Ces fonctions permettent de tester vos valeurs contre certaines conditions. Cette API est inspirée de Jest. Les expectations vous permettent aussi d'écrire vos tests comme une phrase naturelle.

Assertions et expectations peuvent être utilisées dans les fichiers de test Pest mais... les expectations sont plus explicites et intuitives.

**tests/Feature/AnyTest.php**

```php
<?php

test('assert true is true', function () {
    // These two lines do exactly the same. Keep just one...
    $this->assertTrue(true);
    expect(true)->toBeTrue();
});
```

### Utiliser les datasets {#using-datasets}

> <https://pestphp.com/docs/datasets>

On a plusieurs façons de fournir des données à une fonction.

Voici la version [inline](https://pestphp.com/docs/datasets#inline-datasets)

**tests/Feature/AnyTest.php**

```php
it('has emails', function (string $email) {
    expect($email)->not->toBeEmpty();
})->with([
    'enunomaduro@gmail.com',
    'other@example.com'
]);
```

Le dataset est alors un tableau et on peut avoir un tableau multidimensionnel :

**tests/Feature/AnyTest.php**

```php
it('has emails', function (string $name, string $email) {
    expect($email)->not->toBeEmpty();
})->with([
    ['John', 'enunomaduro@gmail.com'],
    ['Other', 'other@example.com']
]);
```

Il existe aussi un moyen de créer un dataset partagé, ce qui est probablement mieux quand le fichier de test devient volumineux (<https://pestphp.com/docs/datasets#shared-datasets>).

### Réutiliser des cas de test PHPUnit sans modification {#reuse-phpunit-tests-cases-without-changes}

C'est vraiment simple : il suffit d'ajouter `/** @test */` comme doc block avant le scénario de test.

Par exemple

**tests/Feature/AnyTest.php**

```php
<?php

declare(strict_types=1);

namespace Tests\Feature;

use Tests\TestCase;

class VisitLoginPageTest extends TestWebCase
{
    /** @test */
    public function test_we_should_see_fields_email_and_password()
    {
        $this->response->assertSee('id="password"', false);
        $this->response->assertSee('id="email"', false);
    }
}
```

Et à partir de maintenant ce test peut être lancé avec `./vendor/bin/pest`.

### Tests architecturaux {#architectural-tests}

> <https://pestphp.com/docs/arch-testing>

Avec Pest (depuis la v2), on peut garantir certaines cohérences architecturales, comme ne pas utiliser de validations dans un controller (via `$request->validate(...)`) mais forcer l'usage de classes Form request à la place.

Le plugin architectural n'aide pas à lancer des tests unitaires mais scanne le projet et s'assure que certaines règles sont respectées.

Les tests architecturaux peuvent ressembler à ceci :

**tests/Feature/AnyTest.php**

```php
test('controllers')
    ->expect('App\Http\Controllers')
    ->not->toUse('Illuminate\Http\Request');

// Models can only be used in a repository
test('models')
    ->expect('App\Models')
    ->toOnlyBeUsedOn('App\Repositories')
    ->toOnlyUse('Illuminate\Database');

test('repositories')
    ->expect('App\Repositories')
    ->toOnlyBeUsedOn('App\Controllers')
    ->toOnlyUse('App\Models');

test('globals')
    ->expect(['dd','dump','var_dump'])
    ->not->toBeUsed();

test('facades')
    ->expect('Illuminate\Support\Facades')
    ->not->toBeUsed()
    ->ignoring('App\Providers');
```

Cette partie est visible en vidéo <https://youtu.be/9EGPo_enEc8?t=1021>

On peut aussi vérifier si une classe est `final` :

**tests/Feature/AnyTest.php**

```php
test('controllers')
    ->expect('App\Http\Controllers')
    ->toUseStrictTypes()
    ->toHaveSuffix('Controller') // or toHavePrefix, ...
    ->toBeReadonly()
    ->toBeClasses() // or toBeInterfaces, toBeTraits, ...
    ->classes->toBeFinal() // 🌶
    ->classes->toExtendNothing() // or toExtend(Controller::class),
    ->classes->toImplementNothing() // or toImplement(ShouldQueue::class),
```

### Prendre des snapshots {#taking-snapshots}

Il y a aussi une fonctionnalité appelée `Snapshots`. L'idée est de stocker un contenu comme snapshot puis de comparer les exécutions futures avec ce snapshot.

Un snapshot peut être le contenu d'une page HTML, une réponse JSON, le contenu d'un fichier / d'un tableau... en fait n'importe quoi (pour un objet, on peut le sérialiser et donc le stocker aussi comme snapshot).

**tests/Feature/AnyTest.php**

```php
it('has a welcome page', function() {
    $response = $this->get('/');
    expect($response)->toMatchSnapshot();
});
```

Au tout premier lancement (`vendor/bin/pest`), le snapshot n'existe pas encore donc il sera créé sur disque et le test sera noté *WARN*.

Le snapshot sera créé dans un sous-dossier du dossier `./tests/.pest/snapshots` (le sous-dossier correspondra à l'emplacement de votre test lancé (p.ex. `Feature/ExampleTest/it_has_a_welcome_page.snap`)).

À partir du deuxième lancement, le snapshot pris sera comparé avec, dans l'exemple ci-dessus, le contenu HTML de la page d'accueil. Dès qu'une différence est détectée (comme la date du jour si elle est présente sur la page), Pest l'affichera dans un diff : la chaîne précédente issue du snapshot et la chaîne récupérée, actuelle.

## Écrire des fonctions globales {#write-global-functions}

On peut écrire nos propres fonctions personnalisées dans le fichier `tests/Pest.php`.

## Le bootstrap de Pest {#pest-bootstrap}

Le fichier `tests/pest.php` peut servir à y placer des fonctions globales mais on devra aussi le mettre à jour si, dans nos fichiers de test, on a besoin d'autres classes.

**tests/Feature/AnyTest.php**

```php
uses(Tests\TestCase::class)->in('Feature');
```

La ligne ci-dessus rendra `Tests\TestCase` disponible dans tous les tests de `tests/Feature`. Si on a besoin de plus de classes, on peut les ajouter :

**tests/Feature/AnyTest.php**

```php
uses(Tests\TestCase::class,Illuminate\Foundation\Testing\RefreshDatabase::class)->in('Feature');
```

Et aussi dans le dossier `tests/Unit` :

**tests/Feature/AnyTest.php**

```php
uses(Tests\TestCase::class)->in('Unit');
```

## Trucs et astuces {#tips-and-tricks}

*Optionnel.*

### Dump and die {#dump-and-die}

On peut utiliser la méthode `dd` pour afficher la valeur de l'expectation courante et arrêter la suite de tests, comme ceci :

**tests/Feature/AnyTest.php**

```php
expect($response)
    ->dd()
    ->toHaveKey('data')
    ->data->toBeEmpty();
```

## Convertir depuis PHPUnit {#convert-from-phpunit}

*Optionnel — pertinent uniquement si vous avez une suite PHPUnit existante à migrer.*

> [Migrate from PHPUnit to Pest - Online migration tool](https://benjamincrozat.com/phpunit-to-pest)

Introduit dans Pest depuis la v2.9, il existe un outil pour convertir de PHPUnit vers Pest.

Voir <https://pestphp.com/docs/pest-spicy-summer-release#content-drift-plugin>

Note : Rector propose aussi un outil : <https://github.com/rectorphp/rector-pest>

Par exemple, le code ci-dessous

**tests/Feature/example\_test.php**

```php
<?php

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

class ExampleTest extends TestCase
{
    public function test_that_true_is_true(): void
    {
        $this->assertTrue(true);
    }
}
```

sera converti en

**tests/Feature/example\_test.php**

```php
test('true is true', function () {
    expect(true)->toBeTrue();
});
```

## Outils {#tools}

### Plugin Laravel {#laravel-plugin}

> [Laravel Pest plugin](https://pestphp.com/docs/plugins/laravel)

Installez le plugin comme ceci : `composer require pestphp/pest-plugin-laravel --dev`

De nouvelles commandes artisan seront alors disponibles :

```bash
$ php artisan | grep pest
 pest
  pest:dataset           Create a new dataset file
  pest:install           Creates Pest resources in your current PHPUnit test suite
  pest:test              Create a new test file
```

### Extensions Visual Studio Code {#visual-studio-code-add-on}

* S'il n'est pas déjà installé, [PHP Intelephense](https://marketplace.visualstudio.com/items?itemName=bmewburn.vscode-intelephense-client) vous permettra d'appuyer sur `F12` sur un nom de méthode (comme `toBeTrue`) et de sauter à l'endroit où la méthode est implémentée,
* [Better Pest](https://marketplace.visualstudio.com/items?itemName=m1guelpf.better-pest) et
* [Pest Snippets](https://marketplace.visualstudio.com/items?itemName=dansysanalyst.pest-snippets)

#### Better Pest avec Docker {#better-pest-with-docker}

Si vous utilisez Docker, pensez à ajouter les lignes suivantes dans votre fichier de configuration `.vscode/settings.json` :

**.vscode/settings.json**

```json
{
    "better-pest.docker.enable": true,
    "better-pest.docker.command": "docker compose exec -u root:root app",
    "better-pest.docker.paths": {
        "/your/local/path": "/your/remote/path"
    }
}
```

Pensez à adapter le nom de votre container (`app` ici) et les chemins :

* `/your/local/path` est l'endroit où votre repository est stocké, sur votre machine hôte,
* `/your/remote/path` est le path dans votre container, probablement `/var/www/html`.

Maintenant, ouvrez simplement n'importe quel fichier Pest et appuyez sur `CTRL`+`Shift`+`P` pour ouvrir la Command Palette. Commencez à taper `Better Pest` et sélectionnez l'option souhaitée (comme `Better Pest: run` pour lancer le fichier).

### Convertir de PHPUnit vers Pest {#convert-from-phpunit-to-pest}

Le repository <https://github.com/mandisma/pest-converter> propose un **PHPUnit to Pest Converter** : PestConverter est une librairie PHP pour convertir des tests PHPUnit en tests Pest.

## Conclusion {#conclusion}

Pest ne remplace pas PHPUnit, il remplace juste le *cérémonial* — `expect()->toBeTrue()` au lieu
d'assertions verbeuses, les datasets et les tests architecturaux offerts, et chaque cas de test
PHPUnit existant fonctionne toujours sans modification. Installez-le une fois, écrivez le prochain
test dans la syntaxe expressive, et migrez le reste seulement quand ça vous arrange.

## Liens {#links}

### Vidéos {#videos}

* [Laracast - Pest](https://laracasts.com/series/jeffreys-larabits/episodes/30)
* [Laracast - Pest from Scratch](https://laracasts.com/series/pest-from-scratch)
* [Laracon IN 2023 : le futur de PEST](https://youtu.be/9EGPo_enEc8)
