---
title: "Test errático (inestable)"
type: "test-smell"
slug: "erratic-test"
url: "http://localhost:3000/es/test-smells/erratic-test.md"
category: "Olores erráticos"
description: "Un test errático (inestable) pasa y falla de forma intermitente sobre el mismo código porque su resultado depende del tiempo, el orden, el estado compartido u otros factores no deterministas en lugar del comportamiento bajo prueba."
---
# Test errático (inestable)

> Un test errático (inestable) pasa y falla de forma intermitente sobre el mismo código porque su resultado depende del tiempo, el orden, el estado compartido u otros factores no deterministas en lugar del comportamiento bajo prueba.

## Signs and Symptoms

Un test que está **verde en una ejecución y rojo en la siguiente sin ningún cambio de código** es errático. Lo reconoces tanto por el comportamiento humano a su alrededor como por el código: los desarrolladores reejecutan el CI "para que pase", añaden `@Flaky`/`retry(3)` o ponen el test en cuarentena en lugar de arreglarlo.

Señales habituales en el código y en el patrón de fallo:

* **Temporización/asincronía:** "esperas" con `sleep`/`setTimeout`, promesas sin esperar (await) o aserciones que compiten con el sistema bajo prueba. Los fallos se correlacionan con la velocidad de la máquina o la carga del CI.
* **Dependencia del orden:** el test pasa en aislamiento pero falla en la suite completa, o viceversa. Barajar el orden de los tests cambia el resultado.
* **Estado mutable compartido:** colecciones estáticas, una fila de BD reutilizada, un singleton o un fixture mutado por un test anterior.
* **Entradas no deterministas:** `Date.now()`/`new Date()`, `Math.random()`, configuración regional/zona horaria, orden de iteración de un hash-map o IDs autogenerados.
* **Recursos externos:** red, sistema de archivos o reloj reales. Los fallos parecen timeouts o "connection refused", no discrepancias de aserción.
* **Aserciones condicionales:** `expect` oculto dentro de `if`/`catch`/callbacks, de modo que el test pasa en silencio cuando la rama nunca se ejecuta.

```js
// Olor: una "espera" fija, un arreglo compartido y un reloj real
const created = [];                       // compartido entre tests → tests que interactúan

test('shows a fresh receipt', async () => {
  render(<Checkout />);
  fireEvent.click(screen.getByText('Pay'));
  await new Promise(r => setTimeout(r, 300));   // se espera que la petición haya terminado
  created.push('order-1');                       // se filtra a tests posteriores
  expect(screen.getByRole('status'))
    .toHaveTextContent(`Paid ${new Date().toISOString()}`); // cambia en cada ejecución
});

```

Esto se corresponde directamente con los subolores del _Test errático_ de Meszaros: **Tests que interactúan / Guerra de ejecución de tests** (estado compartido), **Test solitario** (solo se ejecuta tras otro), **Optimismo de recursos** (asume que un recurso externo está ahí), **Fuga de recursos** (no limpia) y **Test no determinista / irrepetible** (tiempo, aleatoriedad, asincronía).

## Reasons for the Problem

**Por qué ocurre**

* **Temporización implícita.** El código asíncrono se "sincroniza" con `sleep` fijos o sin esperar (await) en absoluto. El retardo es una conjetura: suficiente hoy, demasiado corto mañana bajo carga.
* **Acoplamiento oculto.** Los tests comparten una base de datos, un singleton, variables a nivel de módulo o archivos en disco. Los efectos secundarios de un test se convierten en las precondiciones de otro (los _Tests que interactúan_ / _Guerra de ejecución de tests_ de Meszaros), así que los resultados dependen del orden y de lo que se ejecutó en paralelo.
* **Se cuelan entradas no deterministas.** El tiempo real del reloj, `Math.random()`, la configuración regional/zona horaria y las colecciones sin orden varían entre ejecuciones y máquinas.
* **Dependencia optimista del entorno.** Los tests acceden a una red/servicio en vivo o asumen que un archivo existe (_Optimismo de recursos_) y nunca liberan lo que adquieren (_Fuga de recursos_).
* **Aserciones que pueden saltarse.** Poner `expect` dentro de un condicional o de un `.catch` significa que la comprobación puede no ejecutarse nunca, así que el test "pasa" por accidente.

**Por qué hace daño**

* **Falsa confianza y defectos enmascarados.** Un test inestable puede fallar por razones ajenas a producción, _y_ una regresión real puede esconderse tras un fallo que todos asumen que es "solo inestabilidad". Ya no puedes distinguir la señal del ruido.
* **Erosión de la confianza.** Una vez que se sabe que una suite es inestable, los desarrolladores ignoran las compilaciones en rojo y reintentan por reflejo, lo que entrena al equipo para desestimar la suite de tests por completo.
* **Tiempo desperdiciado y pipelines rotos.** Los reintentos, las reejecuciones y las investigaciones de "¿soy yo o es el test?" ralentizan a todos y bloquean el CI/CD por cuestiones que no lo son.
* **Mantenibilidad pobre.** Los tests erráticos son difíciles de depurar porque el fallo no es reproducible; tienden a desactivarse en vez de arreglarse, reduciendo en silencio la cobertura real.

## Treatment

Trata la inestabilidad como un defecto del test, no como una rareza que sortear con reintentos. Los reintentos sirven para **detectar** la inestabilidad, nunca para **ocultarla**. Pon el test en cuarentena si bloquea el pipeline y luego busca la causa raíz.

**1\. Reemplaza los sleeps por espera basada en condiciones.** Sondea el estado que de verdad te importa en lugar de adivinar una duración.

```js
// antes — retardo fijo y propenso a carreras
fireEvent.click(screen.getByText('Pay'));
await new Promise(r => setTimeout(r, 300));
expect(screen.getByRole('status')).toBeInTheDocument();

// después — espera la condición y luego comprueba
fireEvent.click(screen.getByText('Pay'));
expect(await screen.findByRole('status')).toBeInTheDocument();

```

**2\. Haz deterministas las entradas.** Inyecta el reloj o usa temporizadores falsos; siembra o haz stub de la aleatoriedad; fija la configuración regional/zona horaria.

```js
// antes: depende de la fecha real
expect(label).toBe(`Paid ${new Date().toISOString()}`);

// después: congela el tiempo (vitest/jest)
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-06-15T00:00:00Z'));

```

**3\. Aísla cada test.** Da a cada test fixtures nuevos, reinicia el estado compartido (BD, singletons, globales de módulo) en `beforeEach`/`afterEach` y evita las variables mutables a nivel de módulo. Ejecuta la suite en **orden aleatorio** (p. ej. `--seed`/randomize de Jest, `MethodOrderer.Random` de JUnit) para sacar a la luz las dependencias de orden cuanto antes.

**4\. Haz stub de los recursos externos.** Simula la red, el sistema de archivos y las llamadas al sistema para que el test nunca dependa de que un servicio esté disponible (cura el _Optimismo de recursos_); libera/limpia siempre los recursos adquiridos (cura la _Fuga de recursos_).

**5\. Espera todo el trabajo asíncrono y comprueba sin condiciones.** Devuelve/espera (return/await) las promesas y las utilidades de test asíncronas; saca los efectos secundarios de los callbacks de `waitFor` para que se ejecuten una sola vez; nunca entierres `expect` dentro de `if`/`catch`.

**6\. Verifica el arreglo.** Ejecuta el test ahora determinista muchas veces (y bajo carga / con el orden barajado) para confirmar la estabilidad antes de sacarlo de cuarentena.

## Detected by

- **sonar** `java:S5973` — Los tests deben ser estables (https://rules.sonarsource.com/java/RSPEC-5973/)
- **sonar** `java:S2925` — "Thread.sleep" no debe usarse en los tests (https://rules.sonarsource.com/java/RSPEC-2925/)
- **eslint-jest** `no-conditional-expect` — no-conditional-expect (https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/no-conditional-expect.md)
- **eslint-vitest** `no-conditional-expect` — no-conditional-expect (https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-expect.md)
- **eslint-testing-library** `await-async-queries` — await-async-queries (https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-queries.md)
- **eslint-testing-library** `await-async-utils` — await-async-utils (https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-utils.md)
- **eslint-testing-library** `no-wait-for-side-effects` — no-wait-for-side-effects (https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-wait-for-side-effects.md)
