đ Pest 4 : du browser testing natif, et plus aucune excuse pour ne pas tester
đ Introduction
JâĂ©cris du PHPUnit depuis quâil existe, et jâai mis du temps Ă passer Ă Pest. Pas par dogme, plutĂŽt par inertie : PHPUnit fait le job, mes vieux tests tournent, pourquoi changer. La version 4 me fait changer dâavis. Le browser testing intĂ©grĂ© (basĂ© sur Playwright) tue dâun coup le combo Dusk + Cypress que je traĂźnais comme un boulet sur mes projets Laravel.
Pest 4 demande PHP 8.3+ et reste compatible PHPUnit en dessous. Tu installes le binaire, tu importes ton existant via pest-plugin-drift, et tu commences à écrire les nouveaux tests dans la syntaxe Pest. Pas de big bang. Ce post couvre ce qui change concrÚtement entre PHPUnit et Pest 4, et pourquoi le browser plugin justifie à lui seul la bascule.
đ Install et syntaxe
Lâinstall standard :
composer require pestphp/pest --dev --with-all-dependencies
./vendor/bin/pest --init
./vendor/bin/pestLe --init gĂ©nĂšre un Pest.php Ă la racine de tes tests. Câest ton point de configuration central pour les uses(), les hooks et les helpers globaux.
La syntaxe descriptive vient de Jest et RSpec :
it('crée un utilisateur quand le payload est valide', function () {
$response = $this->postJson('/users', [
'email' => 'arnaud@arkoder.dev',
'name' => 'Arnaud',
]);
expect($response)
->status()->toBe(201)
->and(User::count())->toBe(1);
});
describe('UserController', function () {
it('rejette un email invalide', function () {
$response = $this->postJson('/users', ['email' => 'pas-un-email']);
expect($response->status())->toBe(422);
});
it('rejette un email déjà pris', function () {
User::factory()->create(['email' => 'arnaud@arkoder.dev']);
expect($this->postJson('/users', ['email' => 'arnaud@arkoder.dev'])->status())->toBe(422);
});
});Lisible, sans extends TestCase. Le bĂ©nĂ©fice sâaccumule sur 200 tests. Les messages dâerreur sont aussi plus utiles que la stack PHPUnit standard.
đ Browser testing avec Playwright
Câest la feature majeure de Pest 4. Le plugin pest-plugin-browser lance Playwright derriĂšre une API qui ressemble Ă la syntaxe Pest. Plus besoin de Dusk (et de son setup ChromeDriver Ă lâancienne) ni dâune suite Cypress en parallĂšle.
composer require pestphp/pest-plugin-browser --dev
npm install playwright@latest
npx playwright installUn test de navigation type :
it('permet Ă un utilisateur de se connecter', function () {
User::factory()->create([
'email' => 'arnaud@arkoder.dev',
'password' => 'password',
]);
$page = visit('/')->on()->mobile()->firefox();
$page->click('Sign In')
->assertUrlIs('/login')
->fill('email', 'arnaud@arkoder.dev')
->fill('password', 'password')
->click('Submit')
->assertSee('Dashboard');
});Tu choisis le navigateur (chromium, firefox, webkit), tu peux Ă©muler un device (iPhone14Pro, Macbook14, etc.), et tu te branches sur tes factories Laravel comme dans un test unitaire. Pour les tests qui valident un parcours critique (checkout, signup, dashboard), câest exactement le bon niveau.
Combiné à un job GitHub Actions, tu obtiens du browser testing déterministe en CI sans monter de pipeline parallÚle Cypress.
đ§Ș Architecture testing et snapshots
Deux modules trĂšs utiles :
Architecture testing : tu dĂ©cris des rĂšgles structurelles sur ton code et Pest vĂ©rifie quâelles sont respectĂ©es. Tu enforces une discipline sans relire toute la PR.
arch('Aucun debug en prod')
->expect(['dd', 'dump', 'ray', 'var_dump'])
->not->toBeUsed();
arch('Les contrĂŽleurs n''appellent pas Eloquent directement')
->expect('App\Http\Controllers')
->not->toUse('Illuminate\Database\Eloquent\Builder');Snapshot testing : tu gĂ©nĂšres un snapshot dâune sortie (HTML, JSON, vue Blade compilĂ©e) et chaque exĂ©cution future compare. DĂ©tecte les rĂ©gressions silencieuses sans Ă©crire 30 assertions manuelles.
it('rend la page projets', function () {
$response = $this->get('/projets');
expect($response->content())->toMatchSnapshot();
});Quand le snapshot doit changer (refonte intentionnelle), tu mets Ă jour avec pest -d --update-snapshots.
⥠ParallÚle, mutations, coverage
Pest 4 livre nativement :
--parallel: tes tests tournent sur plusieurs processus. Sur une grosse suite (>500 tests), le gain est massif (3-5Ă). Pas de plugin externe.--mutate: mutation testing intĂ©grĂ©. Pest modifie ton code (un opĂ©rateur, un return) et vĂ©rifie que tes tests cassent. Sinon : ton code est sous-testĂ©. TrĂšs lent Ă exĂ©cuter, donc Ă lancer ponctuellement pour Ă©valuer la qualitĂ© rĂ©elle des tests.--coverage --min=80: la couverture en ligne de commande, avec un seuil minimal. IdĂ©al pour la CI.--profile: repĂšre les 10 tests les plus lents. Utile pour optimiser ce qui mĂ©rite vraiment dâĂȘtre optimisĂ©.
đŻ Quand basculer
- Tu démarres un nouveau projet Laravel ou Symfony : prends Pest direct.
- Tu maintiens une suite PHPUnit existante : installe Pest à cÎté, utilise
pest-plugin-driftpour convertir progressivement. - Tu veux lĂącher Dusk / Cypress : le plugin browser fait le job sans pipeline parallĂšle.
- Tu veux mesurer la qualité réelle de tes tests : lance
--mutateune fois par sprint.
â ïž Quelques prĂ©cautions
- Playwright en CI : la premiĂšre exĂ©cution tĂ©lĂ©charge les binaires (Chromium â 200 Mo). Cache le dossier
~/.cache/ms-playwrightdansactions/cachepour gagner ~2 min par run. - Tests browser flaky : comme partout, les tests browser introduisent du non-déterminisme. Utilise
$page->waitForText()plutĂŽt que dessleep()magiques. - Migration PHPUnit â Pest :
pest-plugin-driftcouvre 90 % des cas mais relis les conversions. LessetUp()deviennent des hooksbeforeEach(). - Mutation testing : trÚs utile en audit, déraisonnable en CI nominal. Lance-le manuellement.
đ Conclusion
Pest 4 fait passer le DX du testing PHP au niveau Jest / Vitest. La syntaxe descriptive est plus agréable, le browser testing élimine une grosse partie du setup Dusk, et les outils annexes (arch, mutation, parallÚle) couvrent ce que tu installais en plugins séparés.
Si tu hĂ©sitais encore, câest probablement le moment. La courbe dâapprentissage est courte si tu connais PHPUnit, et la migration peut se faire test par test sans bloquer le release cycle. Combine avec Pail pour le debug et Boost pour les agents IA et tu obtiens une boucle de feedback locale trĂšs solide.