---
title: "Conditional Test Logic"
type: "test-smell"
slug: "conditional-test-logic"
url: "http://localhost:3000/pt-br/test-smells/conditional-test-logic.md"
category: "Maus Cheiros Obscuros"
description: "Um teste que usa `if`/`switch`/ternários, laços ou `try`/`catch` para decidir o que executar ou verificar, de modo que seu comportamento — e se ele verifica algo de fato — depende de qual ramo é executado em tempo de execução."
---
# Conditional Test Logic

> Um teste que usa `if`/`switch`/ternários, laços ou `try`/`catch` para decidir o que executar ou verificar, de modo que seu comportamento — e se ele verifica algo de fato — depende de qual ramo é executado em tempo de execução.

## Signs and Symptoms

Um teste se lê como um pequeno programa em vez de um script linear "arrange → act → assert". Procure por fluxo de controle dentro do corpo do teste:

* `if`/`else`, `switch`, ternários ou curto-circuitos `&&`/`||` que controlam quais asserções rodam.
* Laços `for`/`while`/`forEach` que constroem entradas ou iteram sobre asserções.
* `try`/`catch` usado para "testar" um caminho de erro, com chamadas `expect` escondidas dentro do `catch`.
* Um único teste reutilizado para vários casos ramificando com base em uma flag ou no estado do ambiente.
* Valores esperados calculados no teste (muitas vezes com um laço ou fórmula) em vez de fixos no código.

```js
// Smell: as asserções ficam dentro de código condicional/ramificado
test('user discount', () => {
  const user = getUser();
  if (user.isPremium) {
    expect(price(user)).toBe(80);   // pode nunca rodar
  } else {
    expect(price(user)).toBe(100);  // pode nunca rodar
  }
});

test('throws on bad input', () => {
  try {
    parse('!!!');
    // se parse() NÃO lançar erro, caímos fora e não verificamos nada → o teste passa
  } catch (err) {
    expect(err.message).toMatch(/invalid/);
  }
});

```

O modo de falha revelador: o teste continua verde mesmo quando o código está quebrado, porque o ramo que contém a asserção nunca foi tomado.

## Reasons for the Problem

**Por que acontece**

* **DRY levado longe demais.** Os autores tentam cobrir vários cenários com um único teste "flexível", ramificando com base nas entradas ou em uma flag em vez de escrever um teste por caso (Meszaros: _Flexible Test_).
* **Acoplamento ao ambiente.** O SUT não foi desacoplado de suas dependências, então o teste se adapta a qualquer estado que encontrar em tempo de execução.
* **Teardown defensivo.** `if (resource) resource.close()` se infiltra para evitar desmontar fixtures que podem não existir (_Complex Teardown_).
* **Expectativas calculadas.** O resultado esperado é derivado com o mesmo algoritmo do código de produção, arrastando essa lógica — laços e tudo — para dentro do teste (_Production Logic in Test_).
* **Teste de caminho de erro na mão.** `try`/`catch` é usado para verificar um erro lançado em vez de um matcher nativo.

**Por que prejudica**

* **Falsa confiança (a pior parte).** A maioria dos executores só reprova um teste quando uma asserção lança erro. Se o ramo que faz a asserção for pulado — ou se o código sob teste não lançar erro dentro de um `try` — o teste passa tendo verificado _nada_.
* **Código de teste sem testes.** Ramos e laços em um teste são lógica que, por si só, não tem testes; um bug no próprio fluxo de controle do teste passa despercebido.
* **Diagnóstico ruim.** Quando um teste com ramificações falha, você precisa primeiro descobrir _qual_ caminho rodou antes de conseguir interpretar a falha.
* **Menor legibilidade e manutenibilidade.** Um teste linear documenta um comportamento com um único resultado esperado; um teste com ramificações força o leitor a simular a execução para saber o que de fato é garantido.
* **Fragilidade.** Testes que dependem do estado de execução/ambiente passam ou falham de forma não determinística.

## Treatment

Faça cada teste ser um único caminho linear e incondicional. Concretamente:

1. **Um cenário por teste.** Quebre um teste com ramificações em testes separados, ou use a API orientada a dados do framework (`test.each`, `it.each`, testes parametrizados) para que cada caso seja sua própria execução, com nome claro e reporte independente.
2. **Eleve as ramificações para fora do teste.** Se um caso só se aplica sob alguma condição, decida isso no momento da _definição_ (por exemplo, `describe`/`it` escolhidos pela configuração), não dentro do corpo do teste — as próprias asserções permanecem incondicionais.
3. **Fixe os valores esperados no código.** Substitua expectativas calculadas por resultados esperados literais (ou um _Expected Object_ / matcher personalizado). Não reimplemente a lógica de produção no teste.
4. **Teste caminhos de erro com matchers,** não com `try`/`catch`: `expect(fn).toThrow(...)`, `await expect(p).rejects.toThrow(...)`. Eles falham de forma evidente quando nenhum erro é lançado.
5. **Se uma asserção condicional for realmente inevitável,** fixe a contagem com `expect.assertions(n)` / `expect.hasAssertions()` para que um ramo pulado falhe em vez de passar silenciosamente.
6. **Substitua o teardown condicional** por hooks de ciclo de vida do framework (`afterEach`) e limpeza automática/idempotente, de modo que nenhum `if` seja necessário para proteger o teardown.

```js
// Antes — teste com ramificações, as asserções podem ser puladas
test('user discount', () => {
  const user = getUser();
  if (user.isPremium) expect(price(user)).toBe(80);
  else                expect(price(user)).toBe(100);
});

// Depois — um caso explícito por linha, toda asserção sempre roda
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 passa quando nada lança erro
try { parse('!!!'); } catch (e) { expect(e.message).toMatch(/invalid/); }

// Depois — falha se parse() não lançar erro
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)
