Pest 5 y PHPUnit 13: qué se rompe al actualizar (y qué falla en silencio)
Tabla de Contenidos
Pest 5 salió a finales de julio de 2026, corre directamente sobre PHPUnit 13 y exige PHP 8.4 como mínimo sin excepciones. Pest en sí mismo no trae rupturas a nivel de API — todo el dolor viene heredado de las reglas de mocking más estrictas de PHPUnit 13. Y lo que de verdad le pega a los equipos no es un error fatal: es que PHPUnit 13 convierte varios patrones de mocking comunes en avisos de deprecación en vez de fallas, así que tu suite puede quedar completamente en verde mientras deja de verificar, en silencio, justo lo que creías que verificaba. Si estás en Laravel 12, hay un segundo problema más contundente: el plugin oficial de Pest para Laravel en su versión 5 pide Laravel 13.23 o superior, así que el plugin ni siquiera se instala.
¿De dónde vienes?
El esfuerzo del salto no es parejo. Depende de cuántas versiones mayores de PHPUnit te estés saltando de un solo golpe.
- Pest 4 (PHPUnit 12) → Pest 5 (PHPUnit 13). Es el salto pensado, el más pequeño. Pest 4 ya pedía PHP 8.3, así que el único piso que subes es de 8.3 a 8.4. Absorbes una sola tanda de cambios de PHPUnit (12 → 13).
- Pest 3 o anterior (PHPUnit 11 o más viejo) → Pest 5. Aquí absorbes dos tandas apiladas: PHPUnit 11 → 12 ya había eliminado los metadatos por docblock (necesitas atributos nativos de PHP:
#[Test],#[DataProvider], etc.) y los mocks de clases abstractas o traits, encima de todo lo que cambia PHPUnit 13 más abajo. Saltar de 3 a 5 en un solocomposer updatees como una actualización de 20 minutos se convierte en una de dos días — pasa primero por 4, o al menos prueba el salto a 12 de forma aislada. - PHPUnit puro, sin Pest, en PHPUnit 12.5. La propia regla de actualización de PHPUnit es explícita: no muevas a 13 si tu suite todavía no corre limpia en 12.5. Si
phpunit --display-deprecationsmuestra algo, arréglalo primero, en su lugar, antes de tocar la restricción de versión de Pest o PHPUnit.
El piso que no se negocia: PHP 8.4
Pest 5 exige PHP 8.4.0 o superior, punto. No hay un “en la práctica funciona en 8.3” como sí existe en algunos saltos de versión de Laravel. Si tu equipo sigue en PHP 8.1, 8.2 u 8.3, eso es trabajo de infraestructura que pasa antes de tocar el composer.json de Pest — no en paralelo.
php -v # necesitas 8.4.0 o superior
Haz el salto de PHP como su propio despliegue, verificado en verde con el stack de pruebas viejo, antes de cambiar también el stack de pruebas. De lo contrario, un CI en rojo después de la actualización no te dice cuál de los dos cambios fue el culpable.
Rupturas, ordenadas por lo que de verdad duele
1. Laravel 12 no puede usar el plugin de Pest 5 para Laravel (bloqueante, no silencioso)
Esta es la que nadie menciona cuando venden la actualización como “solo una línea”, y es lo primero que detiene en seco a un equipo de Laravel. pestphp/pest-plugin-laravel en su versión 5.0.1 declara:
{
"require": {
"php": "^8.4",
"laravel/framework": "^13.23.0",
"pestphp/pest": "^5.0.1"
}
}
Si tu app está en Laravel 12, composer update se va a negar a resolver pest-plugin-laravel ^5.0 — Composer reporta un conflicto, no una falla de prueba misteriosa. La solución no es un problema de Pest: necesitas Laravel 13.23+ antes de que el plugin de Laravel para Pest 5 sea siquiera instalable. Si estás actualizando ambos frentes a la vez, ordénalo — primero Laravel, después Pest — o te vas a pasar una tarde depurando un árbol de dependencias que nunca iba a resolver.
2. any() y with*() sin expects() — la suite que pasa pero deja de verificar (silencioso)
Este es el cambio que más importa, porque nada en tu salida de CI te avisa que ocurrió. PHPUnit 13 deprecra de forma dura dos patrones que antes eran la forma cómoda y por defecto de configurar un mock:
// Antes: funciona, y significa en silencio "cualquier número de llamadas, sin verificar orden"
$mock->method('handle')->with($this->equalTo($payload));
// PHPUnit 13: emite una deprecación, pero igual corre y sigue "pasando"
Las deprecaciones en PHPUnit son avisos, no fallas, a menos que configures explícitamente failOnDeprecation (o failOnPhpunitDeprecation, la propia de PHPUnit) en tu suite. Lo que significa que el escenario realista no es “la actualización rompe el CI el día uno” — es “el CI se queda en verde durante semanas mientras acumula decenas de avisos de deprecación que nadie lee, y después alguien activa el fallo estricto por deprecación, o PHPUnit 14 elimina el patrón de golpe y la suite se pone roja toda junta, en una fecha que tú no controlas”.
La corrección es hacer explícita la intención en vez de depender del comportamiento implícito por defecto:
// Si de verdad quieres verificar que la llamada ocurre:
$mock->expects($this->once())->method('handle')->with($this->equalTo($payload));
// Si no te importa si se llama o no — usa un stub, no un mock:
$stub = $this->createStub(HandlerInterface::class);
$stub->method('handle')->willReturn($result);
El propio matcher any() de conteo de invocaciones queda deprecado de forma dura por la misma razón: combinar expects($this->any()) con un mock es una contradicción — los mocks existen para verificar interacciones, y any() significa “no me importa si esto pasa”. Los propios mantenedores de PHPUnit lo dicen sin rodeos: si no quieres verificar nada, lo que querías era un stub.
3. createPartialMock() de Laravel choca con la regla #2 (silencioso, específico de Laravel)
Es la misma deprecación de arriba, pero vale la pena señalarla aparte porque golpea a un helper que la mayoría de las suites de Laravel usan sin pensarlo dos veces. El trait de testing de Laravel envuelve el createPartialMock() de PHPUnit, y la implementación interna de ese envoltorio dispara la deprecación de “with*() sin expects()” en PHPUnit 13 aunque tu código de prueba se vea perfectamente sano:
// Dispara la deprecación en PHPUnit 13, aunque se vea inocente
$class = $this->createPartialMock(NightlyCommand::class, ['runSequenceOfCommands']);
// Corregido: te saltas el envoltorio y usas el builder de PHPUnit directamente
$class = $this->getMockBuilder(NightlyCommand::class)
->onlyMethods(['runSequenceOfCommands'])
->getMock();
Búscalo con grep antes de actualizar, porque el número de deprecaciones puede ser grande si el patrón es común en tu suite:
grep -rln "createPartialMock" tests/
4. Eliminaciones reales — estas sí fallan, de inmediato, sin ambigüedad
A diferencia de las dos anteriores, estas ya venían deprecadas de forma dura desde PHPUnit 12 y en 13 simplemente ya no existen. Lanzan un Error fatal, no un aviso, así que salen a la luz desde la primera corrida — que en realidad es la categoría fácil de resolver:
// Eliminado en PHPUnit 13 — error fatal, no una deprecación
$this->assertThat($value, $this->isType('string'));
$this->assertContainsOnly('string', $collection);
$this->assertNotContainsOnly('int', $collection);
// Sus reemplazos especializados, disponibles desde PHPUnit 11.5
$this->assertThat($value, $this->isString()); // o directamente assertIsString($value)
$this->assertContainsOnlyString($collection);
$this->assertContainsNotOnlyInt($collection);
// Para colecciones de objetos sigue existiendo la de siempre
$this->assertContainsOnlyInstancesOf(User::class, $collection);
También se eliminó el flag de CLI --dont-report-useless-tests, Configuration::includeTestSuite() / excludeTestSuite(), el atributo #[CoversNothing] en métodos individuales (ahora solo a nivel de clase), el atributo #[RunClassInSeparateProcess], y el soporte para cadenas de restricción de versión sin un operador de comparación explícito. Ninguno de estos es común en una suite típica de Laravel, pero si mantienes un paquete con extensiones propias de PHPUnit o herramientas de CI que invocan phpunit directamente, revisa tus flags.
Si vienes de withConsecutive() específicamente: eso ya es viejo — se eliminó desde PHPUnit 10 — pero PHPUnit 13 por fin trae reemplazos decentes en vez de dejarte escribiendo workarounds incómodos de conteo de llamadas:
// Nuevo en PHPUnit 13
$mock->expects($this->once())
->method('handle')
->withParameterSetsInOrder(['first'], ['second']);
$mock->expects($this->exactly(2))
->method('handle')
->withParameterSetsInAnyOrder(['first'], ['second']);
5. El Test Impact Analysis necesita un driver de cobertura, o simplemente no corre (silencioso)
La función estrella de Pest 5 — el Tia Engine, que promete reducir una suite de Laravel de 10 minutos a unos 4 segundos en corridas posteriores al solo volver a correr las pruebas afectadas por tu diff — necesita PCOV o Xdebug instalado para grabar su grafo de dependencias inicial. Sin uno de los dos, Pest no lanza error ni avisa fuerte; TIA simplemente nunca se activa, y tu suite sigue corriendo completa cada vez, en silencio. Si activaste TIA esperando la mejora de velocidad y no pasó nada, revisa si tienes un driver de cobertura antes de asumir que la función está rota:
php -m | grep -iE "pcov|xdebug"
Para una configuración de equipo, el patrón sensato es que el CI grabe el grafo base una vez por cada merge a main (donde activar un driver de cobertura sale barato) y que las máquinas locales consuman ese grafo en caché, en vez de que cada desarrollador corra una línea base instrumentada con cobertura en su propia máquina.
6. Sin cambios de configuración — de verdad no hay nada que hacer aquí
Vale la pena decirlo de forma explícita porque la gente lo espera: la guía de actualización de Pest 5 documenta ningún cambio en phpunit.xml ni en ningún archivo de configuración de Pest. Si tu suite corre limpia bajo las reglas de PHPUnit 13, el bump de versión en composer.json es toda la migración. Eso es lo bastante raro en este ecosistema como para no darle más vueltas de las necesarias.
¿Cuándo actualizar?
- Estás en Pest 4 / PHPUnit 12, PHP 8.4 ya es tu piso, y
phpunit --display-deprecationssale limpio. Actualiza ahora. Esto se acerca a los “2 minutos” que promete la documentación, y solo TIA ya vale la pena si tu suite pasó de un par de miles de pruebas. - Estás en Pest 4 pero todavía en PHP 8.3. Sube PHP primero, verifica que la suite actual siga en verde y sin deprecaciones en 8.4, y luego sube Pest como un paso aparte.
- Estás en Pest 3 o en PHPUnit 11 o anterior, sin Pest. No te saltes directo a 5. Pasa primero por la tanda de PHPUnit 12 (atributos en vez de docblocks, sin mocks de abstractas ni traits), confirma que la suite está limpia, y toma el salto de 12 a 13 aparte.
- Eres un equipo de Laravel todavía en Laravel 12. El plugin de Laravel para Pest 5 está fuera de tu alcance hasta que estés en Laravel 13.23+. O actualizas Laravel primero, o te quedas en
pest-plugin-laravel ^4.0con Pest 4 hasta que lo hagas — no dejes que el resolvedor de dependencias de Composer tome esa decisión por ti a medio camino. - Cualquiera con uso pesado de mocks en la suite, sin importar la versión. Corre el grep de deprecaciones antes de tocar siquiera el
composer.json. El costo real de esta actualización no es el bump de versión — es descubrir cuántos de tus mocks solo “pasaban” porque en realidad no estaban verificando nada.
Para seguir leyendo:
- Qué se rompe al actualizar a Laravel 13
- PHP 8.6: todas las novedades y qué se rompe al actualizar
- Cómo actualizar un proyecto de Laravel 9 a Laravel 10
Preguntas frecuentes
¿Pest 5 exige PHP 8.4?
Sí. Pest 5 requiere PHP 8.4.0 o superior como mínimo absoluto, sin excepciones para PHP 8.1, 8.2 u 8.3. Esto viene de que Pest 5 corre sobre PHPUnit 13, que a su vez exige PHP 8.4 o posterior.
¿Puedo usar Pest 5 con Laravel 12?
No con el plugin oficial. Desde la versión 5.0.1 de pest-plugin-laravel, el paquete exige laravel/framework ^13.23.0, así que Composer se niega a instalarlo en una aplicación con Laravel 12. Necesitas actualizar a Laravel 13.23 o superior primero, o quedarte en Pest 4 con pest-plugin-laravel ^4.0 mientras tanto.
¿Qué pasa si mi suite de pruebas ya tiene avisos de deprecación de PHPUnit antes de actualizar?
Los avisos de deprecación de PHPUnit no hacen fallar un build por defecto, así que una suite puede aparentar que pasa mientras usa patrones que PHPUnit 13 marca, como el matcher de invocaciones any() o las llamadas con*() sin un expects() explícito. PHPUnit recomienda oficialmente no actualizar a 13 a menos que tu suite ya corra limpia, sin avisos de deprecación, en PHPUnit 12.5, porque esos avisos se convierten en eliminaciones definitivas en una futura versión mayor sin más aviso.
¿Se eliminó el matcher any() en PHPUnit 13?
No, todavía no. El matcher de conteo de invocaciones any() está deprecado de forma dura en PHPUnit 13, lo que significa que todavía funciona pero dispara un aviso de deprecación, y está programado para eliminarse en PHPUnit 14. La corrección recomendada es usar una expectativa explícita como expects($this->once()) si quieres verificar que una llamada ocurre, o cambiar a createStub() si no necesitas verificación en absoluto.
¿Necesito Xdebug o PCOV para usar el Test Impact Analysis de Pest 5?
Sí. El Tia Engine necesita un driver de cobertura de código, ya sea PCOV o Xdebug, para grabar el grafo de dependencias en su primera corrida. Sin uno instalado y activado, el Test Impact Analysis no lanza ningún error; simplemente no se activa, y tu suite sigue corriendo todas las pruebas en cada corrida.
¿Cuánto tarda de verdad la actualización a Pest 5?
La documentación oficial estima unos 2 minutos para el bump de versión en composer.json, y esa estimación es precisa para la parte específica de Pest. El costo real de tiempo está en auditar tu suite contra las reglas de PHPUnit 13 antes de actualizar, sobre todo los patrones de uso de mocks y, para las apps de Laravel, confirmar que ya estás en Laravel 13.23 o superior.