Presentamos Pest PHP, predeterminado desde Laravel 11, desde el punto de vista de un ingeniero senior. Explicamos las diferencias con PHPUnit, el coste de migración y el uso de los tests arch(), pensando en su adopción en proyectos reales.
¿Por qué Pest pasó a ser el valor por defecto en Laravel?
Desde Laravel 11, al crear un proyecto con laravel new, Pest se selecciona como framework de testing por defecto. PHPUnit se ha usado durante años como herramienta de testing estándar de PHP, pero Pest se apoya en PHPUnit y ofrece una notación de tests más concisa y legible.
Como Pest se ejecuta sobre PHPUnit, los tests PHPUnit existentes pueden ejecutarse tal cual. La migración se puede hacer de forma progresiva.
Detrás de la decisión oficial de adoptar Pest está el objetivo de reducir la fricción a la hora de escribir tests. Al eliminar los pasos ceremoniosos de definir una clase, escribir métodos, etc., y ofrecer una notación que permite concentrarse en “lo que quieres testear”, el hábito de escribir tests se adquiere de forma natural.
class AuthTest extends TestCase{ public function test_user_can_login(): void { $user = User::factory()->create(); $response = $this->post('/login', [ 'email' => $user->email, 'password' => 'password', ]); $response->assertRedirect('/dashboard'); }}
La función test() de Pest recibe un closure. Al no necesitar definir clase ni método, la intención del test queda clara desde la primera línea. it() se puede usar de la misma manera. Cuando se escribe en inglés, se lee como una frase natural: it('can login', ...).
La mayor característica de Pest es la aserción en formato encadenado con expect().
test('la API de listado de posts devuelve 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();});
Puedes escribir aserciones en un formato que se lee de forma natural, con expect($value)->toBe(), ->toBeNull(), ->toContain(), ->toHaveCount(), etc. También puedes agrupar varias aserciones con encadenamiento de métodos.
Tests arch() — Comprobación automática de la arquitectura
arch() de Pest son tests que verifican la estructura del código base. Puedes verificar automáticamente reglas de arquitectura como “los controladores no dependen directamente de los modelos” o “los modelos heredan de Eloquent”.
test('los modelos heredan de Eloquent', function () { arch()->expect('App\Models') ->toExtend(Illuminate\Database\Eloquent\Model::class);});test('los controladores están definidos como final', function () { arch()->expect('App\Http\Controllers') ->toBeFinal();});test('las clases de servicio no dependen de los controladores', function () { arch()->expect('App\Services') ->not->toUse('App\Http\Controllers');});
Los tests arch() se ejecutan mediante análisis estático del código fuente. Al no enviar peticiones HTTP reales ni acceder a la base de datos, son muy rápidos.
Pest también incluye presets con reglas de arquitectura de uso frecuente.
test('cumple con las reglas de arquitectura de Laravel', function () { arch()->preset()->laravel();});
El preset laravel() verifica en bloque reglas alineadas con las convenciones habituales de Laravel: nombrado de modelos, herencia de controladores, estructura de middleware, etc.
Como Pest hereda del TestCase de Laravel, puedes usar tal cual todos los helpers de test de Laravel: actingAs(), assertDatabaseHas(), los helpers de tests HTTP, etc.
use App\Models\Post;use App\Models\User;test('el usuario puede borrar sus propios posts', 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('no se pueden borrar posts de otros usuarios', 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);});
También puedes aplicar fácilmente los traits RefreshDatabase o DatabaseTransactions con uses(). Si lo pones al principio del archivo, se aplica a todo el archivo.
Si lo configuras una vez en tests/Pest.php, RefreshDatabase se activa automáticamente en todos los tests Feature. También se puede sobrescribir en archivos de test individuales.
Pest y PHPUnit pueden convivir en el mismo proyecto. No es necesario reescribir los tests PHPUnit existentes; basta con empezar a escribir los tests nuevos en formato Pest.
./vendor/bin/pest # Ejecuta todos los tests con Pest (incluidos los PHPUnit)./vendor/bin/pest --filter="usuario" # Filtra por nombre de testphp artisan test # Con el comando Artisan también funciona Pest
Desde Laravel 11, si Pest está instalado, php artisan test ejecuta automáticamente con Pest.
Primero añade la configuración de uses() en tests/Pest.php
Escribe los nuevos tests en formato Pest
Ve migrando los tests PHPUnit existentes poco a poco, comprobando su funcionamiento
No hace falta reescribir todos los tests de golpe. Como la sintaxis de Pest tiene menor coste de aprendizaje que la de PHPUnit, la adopción por parte del equipo es relativamente fluida.
Pest no es un reemplazo de PHPUnit, sino una capa superior que envuelve a PHPUnit. La integración con Laravel también es profunda, hasta el punto de que se selecciona por defecto al generar los starter kits. El coste de introducción en proyectos existentes es bajo, y solo con probar a escribir los nuevos tests con Pest ya obtienes beneficios.Cuando aumenta la cantidad de tests escritos, se detectan antes los bugs y baja la barrera psicológica para refactorizar. Pest es una herramienta para reducir “la fricción de escribir tests” en sí.
Documentación oficial de Pest
Para todas las funciones de Pest (datasets, cobertura, ejecución en paralelo, etc.), consulta la documentación oficial.