Imagina esta situación: escribes un test, lo ejecutas en local y pasa. Lo subes al repositorio, el CI lo ejecuta y falla. Lo vuelves a ejecutar sin cambiar nada y pasa. Al día siguiente vuelve a fallar.
Bienvenido al mundo de los flaky tests.
¿Qué es un flaky test?
Un flaky test es un test que a veces pasa y a veces falla sin que hayas modificado el código. Su resultado es impredecible y depende de factores externos al propio test.
El término "flaky" viene del inglés y significa "escamoso" o "inestable". Y eso es exactamente lo que son: tests en los que no puedes confiar.
¿Por qué son un problema?
Los flaky tests erosionan la confianza en tu suite de tests. Cuando un test falla de forma intermitente, el equipo empieza a ignorar los fallos pensando que "será el flaky de siempre". Y cuando un fallo real aparece, pasa desapercibido entre el ruido.
Además, ralentizan el desarrollo. Cada vez que el CI falla, alguien tiene que investigar si es un fallo real o simplemente mala suerte.
Causas más comunes
Dependencia del tiempo
Esta es probablemente la causa más frecuente. Si tu código depende de la hora actual y no la controlas en el test, el resultado puede variar:
<?php
// ❌ Este test puede fallar dependiendo de cuándo se ejecute
it('checks if the offer is still valid', function () {
$offer = Offer::factory()->create([
'expires_at' => now()->addHour(),
]);
expect($offer->isValid())->toBeTrue();
});El problema es que entre la creación de la oferta y la comprobación puede pasar tiempo, o el test puede ejecutarse justo en el límite.
La solución es congelar el tiempo:
<?php
// ✅ Test determinista
it('checks if the offer is still valid', function () {
$this->freezeTime();
$offer = Offer::factory()->create([
'expires_at' => now()->addHour(),
]);
expect($offer->isValid())->toBeTrue();
});Orden de ejecución
Si un test depende del estado que dejó otro test anterior, tienes un problema. Los tests deberían poder ejecutarse en cualquier orden y de forma aislada:
<?php
// ❌ Este test depende de que otro test haya creado datos antes
it('counts total users', function () {
expect(User::count())->toBe(5);
});La solución es que cada test prepare su propio estado:
<?php
// ✅ Test independiente
it('counts total users', function () {
User::factory()->count(5)->create();
expect(User::count())->toBe(5);
});Estado compartido entre tests
La base de datos, la caché, el sistema de archivos... cualquier estado que persista entre tests puede causar problemas.
En Laravel, usa RefreshDatabase para asegurar que cada test empieza con una base de datos limpia:
<?php
uses(Illuminate\Foundation\Testing\RefreshDatabase::class);Peticiones HTTP reales
Si tu código hace peticiones HTTP y no las mockeas, el test depende de un servicio externo. Si ese servicio está lento o caído, tu test falla.
Esto conecta directamente con Http::preventStrayRequests(). Si no lo conoces, te recomiendo leer cómo evitar que tus tests hagan peticiones HTTP reales por accidente. Actívalo globalmente y te aseguras de que ningún test hace peticiones reales por accidente:
<?php
beforeEach(function () {
Http::preventStrayRequests();
});Datos aleatorios sin control
Las factories de Laravel generan datos aleatorios por defecto. Esto está bien para la mayoría de casos, pero si tu lógica depende de valores específicos, puede causar problemas:
<?php
// ❌ El nombre generado puede variar y afectar al resultado
it('formats the user display name', function () {
$user = User::factory()->create();
expect($user->displayName)->toContain(' ');
});Si necesitas datos específicos, sobrescríbelos:
<?php
// ✅ Datos controlados
it('formats the user display name', function () {
$user = User::factory()->create([
'first_name' => 'John',
'last_name' => 'Doe',
]);
expect($user->displayName)->toBe('John Doe');
});Race conditions
En tests que involucran colas, eventos asíncronos o procesos en paralelo, el timing puede variar entre ejecuciones. Si quieres profundizar en este tema, tengo un curso sobre race conditions y cómo evitarlas en Laravel.
La solución es usar los fakes de Laravel para ejecutar todo de forma síncrona en tests:
<?php
beforeEach(function () {
Queue::fake();
Event::fake();
});Cómo detectar flaky tests
Si sospechas que tienes flaky tests, puedes ejecutar tu suite varias veces seguidas:
# Ejecutar la suite 10 veces
for i in {1..10}; do php artisan test; doneTambién puedes ejecutar los tests en orden aleatorio para detectar dependencias entre ellos:
php artisan test --randomLa tentación del retry
Pest ofrece ->retry() para reintentar tests que fallan:
<?php
it('does something flaky', function () {
// ...
})->retry(3);Esto puede ser útil como solución temporal, pero no arregla el problema de fondo. Úsalo con precaución y siempre intenta encontrar la causa raíz.
Conclusión
Los flaky tests son uno de los problemas más frustrantes en testing. La clave para evitarlos es escribir tests deterministas: tests cuyo resultado depende únicamente del código que están probando, no de factores externos como el tiempo, el orden de ejecución o servicios externos.
Invierte tiempo en hacer tus tests predecibles. Tu yo del futuro te lo agradecerá.
Curso Laravel 12
Completo 2026
El único curso 100% actualizado que incluye Laravel 12, Livewire 3, Vue 3, React 19 e Inertia 2. Aprende con proyectos reales y las últimas funcionalidades.
star Incluido en cualquier suscripción