---
title: "Conditional Test Logic"
type: "test-smell"
slug: "conditional-test-logic"
url: "http://localhost:3000/fr/test-smells/conditional-test-logic.md"
category: "Odeurs d'obscurité"
description: "Un test qui recourt à des `if`/`switch`/ternaires, à des boucles ou à des `try`/`catch` pour décider quoi exécuter ou vérifier, de sorte que son comportement — et le fait même qu'il vérifie quoi que ce soit — dépend de la branche exécutée à l'exécution."
---
# Conditional Test Logic

> Un test qui recourt à des `if`/`switch`/ternaires, à des boucles ou à des `try`/`catch` pour décider quoi exécuter ou vérifier, de sorte que son comportement — et le fait même qu'il vérifie quoi que ce soit — dépend de la branche exécutée à l'exécution.

## Signs and Symptoms

Un test se lit comme un petit programme plutôt que comme un script linéaire « arrange → act → assert ». Cherchez un flot de contrôle dans le corps du test :

* Des `if`/`else`, `switch`, ternaires, ou des court-circuits `&&`/`||` qui conditionnent les assertions exécutées.
* Des boucles `for`/`while`/`forEach` qui construisent les entrées ou itèrent sur les assertions.
* Un `try`/`catch` utilisé pour « tester » un chemin d'erreur, avec des appels `expect` cachés dans le `catch`.
* Un même test réutilisé pour plusieurs cas en se ramifiant sur un drapeau ou sur l'état de l'environnement.
* Des valeurs attendues calculées dans le test (souvent avec une boucle ou une formule) au lieu d'être codées en dur.

```js
// Mauvaise odeur : les assertions vivent dans du code conditionnel/à branches
test('user discount', () => {
  const user = getUser();
  if (user.isPremium) {
    expect(price(user)).toBe(80);   // pourrait ne jamais s'exécuter
  } else {
    expect(price(user)).toBe(100);  // pourrait ne jamais s'exécuter
  }
});

test('throws on bad input', () => {
  try {
    parse('!!!');
    // si parse() ne lève PAS, on passe sans rien vérifier → le test passe
  } catch (err) {
    expect(err.message).toMatch(/invalid/);
  }
});

```

Le mode d'échec révélateur : le test reste vert même quand le code est cassé, parce que la branche contenant l'assertion n'a jamais été empruntée.

## Reasons for the Problem

**Pourquoi cela se produit**

* **Le principe DRY poussé trop loin.** Les auteurs tentent de couvrir plusieurs scénarios avec un seul test « flexible », en se ramifiant selon les entrées ou un drapeau au lieu d'écrire un test par cas (Meszaros : _Flexible Test_).
* **Couplage à l'environnement.** Le SUT n'a pas été découplé de ses dépendances, de sorte que le test s'adapte à l'état qu'il rencontre à l'exécution.
* **Démontage défensif.** Un `if (resource) resource.close()` s'insère pour éviter de démonter des fixtures qui pourraient ne pas exister (_Complex Teardown_).
* **Valeurs attendues calculées.** Le résultat attendu est dérivé avec le même algorithme que le code de production, ce qui fait entrer cette logique — boucles comprises — dans le test (_Production Logic in Test_).
* **Test du chemin d'erreur à la main.** Un `try`/`catch` sert à vérifier une erreur levée au lieu d'un matcher intégré.

**Pourquoi c'est nuisible**

* **Fausse confiance (le pire).** La plupart des exécuteurs ne font échouer un test que lorsqu'une assertion lève une exception. Si la branche qui vérifie est ignorée — ou si le code testé ne lève rien dans le `try` — le test passe sans avoir vérifié _quoi que ce soit_.
* **Code de test non testé.** Les branches et les boucles d'un test constituent une logique qui n'est elle-même pas testée ; un bogue dans le flot de contrôle du test passe inaperçu.
* **Diagnostic médiocre.** Quand un test à branches échoue, vous devez d'abord déterminer _quel_ chemin s'est exécuté avant de pouvoir interpréter l'échec.
* **Lisibilité et maintenabilité réduites.** Un test linéaire documente un comportement avec un seul résultat attendu ; un test à branches oblige le lecteur à simuler l'exécution pour savoir ce qui est réellement garanti.
* **Fragilité.** Les tests qui dépendent de l'état d'exécution/de l'environnement passent ou échouent de façon non déterministe.

## Treatment

Faites de chaque test un chemin unique, inconditionnel et linéaire. Concrètement :

1. **Un scénario par test.** Scindez un test à branches en tests distincts, ou utilisez l'API pilotée par les données du framework (`test.each`, `it.each`, tests paramétrés) pour que chaque cas soit une exécution clairement nommée et rapportée indépendamment.
2. **Sortez les branches hors du test.** Si un cas ne s'applique que sous certaines conditions, décidez-le au moment de la _définition_ (par ex. un `describe`/`it` choisi selon la configuration), pas dans le corps du test — les assertions elles-mêmes restent inconditionnelles.
3. **Codez en dur les valeurs attendues.** Remplacez les valeurs attendues calculées par des résultats attendus littéraux (ou un _Expected Object_ / matcher personnalisé). Ne réimplémentez pas la logique de production dans le test.
4. **Testez les chemins d'erreur avec des matchers,** pas avec `try`/`catch` : `expect(fn).toThrow(...)`, `await expect(p).rejects.toThrow(...)`. Ceux-ci échouent bruyamment quand aucune erreur n'est levée.
5. **Si une assertion conditionnelle est vraiment inévitable,** figez le décompte avec `expect.assertions(n)` / `expect.hasAssertions()` pour qu'une branche ignorée échoue au lieu de passer silencieusement.
6. **Remplacez le démontage conditionnel** par les hooks de cycle de vie du framework (`afterEach`) et un nettoyage automatique/idempotent, afin qu'aucun `if` ne soit nécessaire pour protéger le démontage.

```js
// Avant — test à branches, des assertions peuvent être ignorées
test('user discount', () => {
  const user = getUser();
  if (user.isPremium) expect(price(user)).toBe(80);
  else                expect(price(user)).toBe(100);
});

// Après — un cas explicite par ligne, chaque assertion s'exécute toujours
test.each([
  ['premium', { isPremium: true },  80],
  ['regular', { isPremium: false }, 100],
])('price for %s user', (_label, user, expected) => {
  expect(price(user)).toBe(expected);
});

// Avant — try/catch qui passe quand rien n'est levé
try { parse('!!!'); } catch (e) { expect(e.message).toMatch(/invalid/); }

// Après — échoue si parse() ne lève pas
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)
