---
title: "Conditional Test Logic"
type: "test-smell"
slug: "conditional-test-logic"
url: "http://localhost:3000/es/test-smells/conditional-test-logic.md"
category: "Olores oscuros"
description: "Una prueba que usa `if`/`switch`/ternarios, bucles o `try`/`catch` para decidir qué ejecutar o aseverar, de modo que su comportamiento (y si verifica algo siquiera) depende de qué rama se ejecute en tiempo de ejecución."
---
# Conditional Test Logic

> Una prueba que usa `if`/`switch`/ternarios, bucles o `try`/`catch` para decidir qué ejecutar o aseverar, de modo que su comportamiento (y si verifica algo siquiera) depende de qué rama se ejecute en tiempo de ejecución.

## Signs and Symptoms

Una prueba se lee como un pequeño programa en lugar de como un guion lineal "preparar → actuar → aseverar". Busca flujo de control dentro del cuerpo de la prueba:

* `if`/`else`, `switch`, ternarios o cortocircuitos `&&`/`||` que controlan qué aserciones se ejecutan.
* Bucles `for`/`while`/`forEach` que construyen entradas o iteran sobre aserciones.
* `try`/`catch` usado para "probar" una ruta de error, con las llamadas a `expect` escondidas dentro del `catch`.
* Una misma prueba reutilizada para varios casos ramificando según un flag o el estado del entorno.
* Valores esperados calculados en la prueba (a menudo con un bucle o una fórmula) en lugar de codificados de forma literal.

```js
// Olor: las aserciones viven dentro de código condicional/ramificado
test('user discount', () => {
  const user = getUser();
  if (user.isPremium) {
    expect(price(user)).toBe(80);   // puede no ejecutarse nunca
  } else {
    expect(price(user)).toBe(100);  // puede no ejecutarse nunca
  }
});

test('throws on bad input', () => {
  try {
    parse('!!!');
    // si parse() NO lanza, caemos hasta aquí y no aseveramos nada → la prueba pasa
  } catch (err) {
    expect(err.message).toMatch(/invalid/);
  }
});

```

El modo de fallo revelador: la prueba sigue en verde aunque el código esté roto, porque nunca se tomó la rama que contenía la aserción.

## Reasons for the Problem

**Por qué ocurre**

* **DRY llevado al extremo.** Los autores intentan cubrir varios escenarios con una única prueba "flexible", ramificando según las entradas o un flag en lugar de escribir una prueba por caso (Meszaros: _Flexible Test_).
* **Acoplamiento al entorno.** El SUT no se desacopló de sus dependencias, así que la prueba se adapta a cualquier estado que encuentre en tiempo de ejecución.
* **Teardown defensivo.** Aparece `if (resource) resource.close()` para evitar desmontar fixtures que quizá no existan (_Complex Teardown_).
* **Expectativas calculadas.** El resultado esperado se deriva con el mismo algoritmo que el código de producción, arrastrando esa lógica (bucles incluidos) hasta la prueba (_Production Logic in Test_).
* **Prueba manual de la ruta de error.** Se usa `try`/`catch` para aseverar sobre un error lanzado en lugar de un matcher integrado.

**Por qué es perjudicial**

* **Falsa confianza (lo peor).** La mayoría de los runners solo hacen fallar una prueba cuando una aserción lanza. Si se omite la rama que asevera, o el código bajo prueba no lanza dentro del `try`, la prueba pasa sin haber verificado _nada_.
* **Código de prueba no probado.** Las ramas y los bucles dentro de una prueba son lógica que, a su vez, no tiene pruebas; un error en el propio flujo de control de la prueba pasa desapercibido.
* **Diagnósticos pobres.** Cuando una prueba con ramificaciones falla, primero debes averiguar _qué_ camino se ejecutó antes de poder interpretar el fallo.
* **Menor legibilidad y mantenibilidad.** Una prueba lineal documenta un comportamiento con un único resultado esperado; una prueba con ramificaciones obliga al lector a simular la ejecución para saber qué se garantiza en realidad.
* **Fragilidad.** Las pruebas que dependen del estado del entorno o de la ejecución pasan o fallan de forma no determinista.

## Treatment

Haz que cada prueba sea un único camino lineal e incondicional. En concreto:

1. **Un escenario por prueba.** Divide una prueba con ramificaciones en pruebas separadas, o usa la API basada en datos del framework (`test.each`, `it.each`, pruebas parametrizadas) para que cada caso sea su propia ejecución, con nombre claro e informada de forma independiente.
2. **Saca la ramificación fuera de la prueba.** Si un caso solo aplica bajo cierta condición, decídelo en tiempo de _definición_ (por ejemplo, eligiendo `describe`/`it` según la configuración), no dentro del cuerpo de la prueba; las aserciones en sí permanecen incondicionales.
3. **Codifica los valores esperados de forma literal.** Sustituye las expectativas calculadas por resultados esperados literales (o un _Expected Object_ / matcher personalizado). No reimplementes la lógica de producción en la prueba.
4. **Prueba las rutas de error con matchers,** no con `try`/`catch`: `expect(fn).toThrow(...)`, `await expect(p).rejects.toThrow(...)`. Estos fallan de forma sonora cuando no se lanza ningún error.
5. **Si una aserción condicional es realmente inevitable,** fija el recuento con `expect.assertions(n)` / `expect.hasAssertions()` para que una rama omitida falle en lugar de pasar en silencio.
6. **Sustituye el teardown condicional** por los hooks de ciclo de vida del framework (`afterEach`) y una limpieza automática/idempotente, de modo que no haga falta ningún `if` para proteger el desmontaje.

```js
// Antes: prueba con ramificaciones, las aserciones pueden omitirse
test('user discount', () => {
  const user = getUser();
  if (user.isPremium) expect(price(user)).toBe(80);
  else                expect(price(user)).toBe(100);
});

// Después: un caso explícito por fila, cada aserción se ejecuta siempre
test.each([
  ['premium', { isPremium: true },  80],
  ['regular', { isPremium: false }, 100],
])('price for %s user', (_label, user, expected) => {
  expect(price(user)).toBe(expected);
});

// Antes: try/catch que pasa cuando no se lanza nada
try { parse('!!!'); } catch (e) { expect(e.message).toMatch(/invalid/); }

// Después: falla si parse() no lanza
expect(() => parse('!!!')).toThrow(/invalid/);

```

## Detected by

- **eslint-jest** `jest/no-conditional-expect` — no-conditional-expect (https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/no-conditional-expect.md)
- **eslint-jest** `jest/no-conditional-in-test` — no-conditional-in-test (https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/no-conditional-in-test.md)
- **eslint-vitest** `vitest/no-conditional-expect` — no-conditional-expect (https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-expect.md)
- **eslint-vitest** `vitest/no-conditional-in-test` — no-conditional-in-test (https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-in-test.md)
- **eslint-vitest** `vitest/no-conditional-tests` — no-conditional-tests (https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-tests.md)
