Vai al contenuto principale

Perché Pest è diventato il default di Laravel

Da Laravel 11 in poi, quando crei un progetto con laravel new, Pest viene selezionato come framework di test predefinito. PHPUnit è stato per anni lo strumento standard per i test PHP, ma Pest si basa su PHPUnit offrendo una scrittura di test più concisa e leggibile.
Pest funziona sopra PHPUnit, quindi i test PHPUnit esistenti si eseguono così come sono. La migrazione può procedere gradualmente.
Dietro l’adozione ufficiale di Pest c’è l’obiettivo di ridurre l’attrito nello scrivere test. Eliminando la procedura rituale di definire una classe e scrivere un metodo, e fornendo una scrittura che permette di concentrarsi su “ciò che vogliamo testare”, diventa naturale acquisire l’abitudine di scrivere test.

Differenze principali rispetto a PHPUnit

Scrittura del test

La differenza più evidente è il modo in cui si scrivono i test.
test('l\'utente può effettuare il login', function () {
    $user = User::factory()->create();

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

    $response->assertRedirect('/dashboard');
});
La funzione test() di Pest riceve una closure. Non serve definire classi o metodi e l’intento del test è chiaro fin dalla prima riga. Si può usare anche it(). In inglese si legge come una frase naturale: it('can login', ...).

API expect()

Il tratto distintivo di Pest sono le asserzioni in stile chain con expect().
test('l\'API elenco post ritorna 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() e simili permettono di scrivere asserzioni che si leggono in modo naturale. Puoi anche concatenare più asserzioni.
expect($user)
    ->name->toBe('Mario Rossi')
    ->email->toBe('[email protected]')
    ->is_admin->toBeFalse();

Setup e teardown

Il corrispondente di setUp() di PHPUnit è beforeEach(), quello di tearDown() è afterEach().
beforeEach(function () {
    $this->user = User::factory()->create();
    $this->actingAs($this->user);
});

test('un utente autenticato può accedere al profilo', function () {
    $response = $this->get('/profile');
    $response->assertOk();
});

test('un utente autenticato può aggiornare il profilo', function () {
    $response = $this->patch('/profile', ['name' => 'Nuovo nome']);
    $response->assertRedirect('/profile');
});

Dataset — test table-driven

Se vuoi eseguire la stessa logica di test su più dati, usa dataset.
test('un indirizzo email non valido causa errore di validazione', function (string $email) {
    $response = $this->postJson('/api/users', [
        'name' => 'Mario Rossi',
        'email' => $email,
    ]);

    $response->assertUnprocessable();
})->with([
    'nessuna email' => [''],
    'formato non valido' => ['notanemail'],
    'doppia @' => ['a@@example.com'],
]);
Le label di ciascun dataset ('nessuna email', ecc.) vengono aggiunte al nome del test, così vedi a colpo d’occhio quale caso è fallito.

Test arch() — verifica automatica dell’architettura

arch() di Pest è un test che verifica la struttura della codebase. Puoi verificare automaticamente regole architetturali come “il controller non dipende direttamente dal modello” o “il modello estende Eloquent”.
test('i modelli estendono Eloquent', function () {
    arch()->expect('App\Models')
        ->toExtend(Illuminate\Database\Eloquent\Model::class);
});

test('i controller sono final', function () {
    arch()->expect('App\Http\Controllers')
        ->toBeFinal();
});

test('le classi service non dipendono dai controller', function () {
    arch()->expect('App\Services')
        ->not->toUse('App\Http\Controllers');
});
I test arch() vengono eseguiti effettuando analisi statica del codice sorgente. Non inviano richieste HTTP né accedono al database, quindi sono estremamente veloci.
Pest include anche preset di regole architetturali comunemente usate.
test('rispetta le regole di architettura di Laravel', function () {
    arch()->preset()->laravel();
});
Il preset laravel() verifica insieme regole conformi alle convenzioni tipiche di Laravel come naming dei modelli, ereditarietà dei controller e struttura dei middleware.

Esempi pratici in un progetto Laravel

Creazione del test

php artisan make:test UserTest
Da Laravel 11 in poi, con la configurazione predefinita, viene generato un file di test in formato Pest.
<?php

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

Usare gli helper di test di Laravel senza modifiche

Pest eredita il TestCase di Laravel, quindi puoi usare così come sono tutti gli helper di test di Laravel come actingAs(), assertDatabaseHas() e gli helper di test HTTP.
use App\Models\Post;
use App\Models\User;

test('l\'utente può eliminare i propri post', 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('non si possono eliminare i post di altri utenti', 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);
});

Factory e transazioni database

Anche i trait RefreshDatabase o DatabaseTransactions si applicano facilmente con uses(). Scritto in cima al file si applica all’intero file.
uses(Tests\TestCase::class, Illuminate\Foundation\Testing\RefreshDatabase::class)->in('Feature');
Impostandolo una volta in tests/Pest.php, RefreshDatabase viene abilitato automaticamente per tutti i test Feature. Puoi anche sovrascriverlo nel singolo file di test.

Come convivere con i test PHPUnit esistenti

Pest e PHPUnit possono convivere nello stesso progetto. Non è necessario riscrivere i test PHPUnit esistenti: basta iniziare a scrivere i nuovi test in stile Pest.
./vendor/bin/pest       # Esegui tutti i test con Pest (inclusi quelli PHPUnit)
./vendor/bin/pest --filter="utente"  # Filtra per nome del test
php artisan test        # Anche il comando Artisan usa Pest
Da Laravel 11 in poi, php artisan test usa automaticamente Pest se installato.

Come procedere con la migrazione

  1. Aggiungi prima la configurazione di uses() in tests/Pest.php
  2. Scrivi i nuovi test in stile Pest
  3. Migra gradualmente i test PHPUnit esistenti verificando il funzionamento
Non serve riscrivere tutti i test in fretta. La sintassi di Pest ha un costo di apprendimento inferiore rispetto a PHPUnit, quindi anche l’adozione da parte del team è relativamente fluida.

Conclusioni

ConfrontoPHPUnitPest
Definizione dei testClasse + metodoFunzione test() / it()
Asserzioni$this->assert*()expect()->to*()
SetupsetUp()beforeEach()
Data-driven@dataProvider->with()
Verifica architetturaleAssentearch()
Velocità di esecuzioneStandardEquivalente (gira su PHPUnit)
Pest non è una sostituzione di PHPUnit, ma un layer superiore che lo avvolge. Ha un’integrazione profonda con Laravel, al punto da essere selezionato di default dallo starter kit. Il costo di introduzione nei progetti esistenti è basso e basta iniziare dai nuovi test per beneficiarne. Aumentando la quantità di test scritti, si scoprono prima i bug e cala la barriera psicologica verso il refactoring. Pest è lo strumento che riduce “l’attrito stesso dello scrivere test”.

Documentazione ufficiale di Pest

Per tutte le funzionalità di Pest come dataset, coverage ed esecuzione parallela, consulta la documentazione ufficiale.
Ultima modifica il 13 luglio 2026