---
title: "Test obscur"
type: "test-smell"
slug: "obscure-test"
url: "http://localhost:3000/fr/test-smells/obscure-test.md"
category: "Odeurs d'obscurité"
description: "Un Test obscur est un test où le lecteur ne peut pas déterminer, à partir de la seule méthode de test, quel scénario est mis en place, quel comportement est exercé et quel résultat est attendu — parce que l'intention est enfouie sous trop de détails, trop peu de contexte, ou une logique cachée ailleurs."
---
# Test obscur

> Un Test obscur est un test où le lecteur ne peut pas déterminer, à partir de la seule méthode de test, quel scénario est mis en place, quel comportement est exercé et quel résultat est attendu — parce que l'intention est enfouie sous trop de détails, trop peu de contexte, ou une logique cachée ailleurs.

## Signs and Symptoms

Vous lisez un test et vous ne pouvez toujours pas répondre à trois questions : _quelle est la configuration, qu'est-ce qui est exercé, et quel est le résultat attendu ?_ La chaîne cause/effet est cachée. Meszaros regroupe les causes en « trop d'informations » et « trop peu d'informations ». Indices courants :

* **Test avide** — une seule méthode de test vérifie de nombreux comportements sans rapport, si bien qu'aucune intention unique n'est claire.
* **Invité mystère** — la fixture ou les valeurs attendues vivent hors du test (un fichier, un enregistrement de BD partagé, une fixture chargée par son nom), de sorte que vous ne voyez pas pourquoi l'assertion devrait passer.
* **Fixture générale** — un gros `beforeEach`/factory partagé construit bien plus que ce dont ce test a besoin ; les quelques champs pertinents se perdent dans le bruit.
* **Informations non pertinentes** — des pages de données de configuration ou un snapshot géant alors qu'un ou deux champs seulement comptent réellement.
* **Données de test codées en dur** — des littéraux magiques (`42`, `"a3f9-..."`, `userId=7`) sans signification nommée, répétés entre la configuration et les assertions.
* **Test indirect** — le test sollicite le système sous test à travers plusieurs autres objets, obscurcissant ce qui est réellement testé.

```js
// Obscur : Avide + Invité mystère + Informations non pertinentes
test('user', async () => {
  const data = loadFixture('users.json');        // invité mystère : les valeurs vivent dans un fichier
  const svc = new UserService(data, cfg, clock); // fixture générale : la majeure partie est inutilisée
  const u = await svc.create({ ...data[0], roles: ['a','b'], flags: 0x1f });

  expect(u.id).toBeDefined();
  expect(u.email).toContain('@');
  expect(await svc.count()).toBe(data.length + 1); // pourquoi ce nombre ? la réponse est dans le fichier
  expect(await svc.login(u.email, 'p@ss')).toBe(true); // second comportement sans rapport
});

```

Ici vous ne pouvez pas dire ce que le test prouve sans ouvrir `users.json`, et il vérifie en réalité la création _et_ la connexion à la fois.

## Reasons for the Problem

**Pourquoi cela se produit**

* Les tests grossissent par accrétion : un développeur ajoute « une assertion de plus » à un test existant au lieu d'en écrire un nouveau, produisant un Test avide.
* Partager la configuration paraît efficace, alors une seule fixture large ou un `beforeEach` est réutilisé partout (Fixture générale), et des fichiers externes/données de BD sont chargés par leur nom (Invité mystère).
* Le copier-coller de données d'apparence réaliste traîne avec lui des dizaines de champs non pertinents et de nombres magiques.
* Le sur-mockage ou le passage par de nombreux collaborateurs transforme un test unitaire en Test indirect.

**Pourquoi c'est nuisible**

* **Lisibilité :** un test est la documentation du comportement attendu. Si le lecteur ne peut pas reconstituer Préparer → Exercer → Vérifier, cette valeur documentaire est perdue.
* **Maintenabilité :** quand un test obscur échoue, vous ne savez pas quel comportement a cassé ni si c'est le test ou le code qui a tort, si bien que les changements sont lents et risqués.
* **Fiabilité :** l'Invité mystère et la Fixture générale couplent le test à un état externe/partagé, provoquant des échecs instables (flaky) ou dépendants de l'ordre (et brisant la propriété « fraîche et déterministe » d'un bon test unitaire).
* **Fausse confiance :** les Tests avides masquent la couverture — un échec en début de méthode court-circuite les vérifications ultérieures, de sorte que des comportements que vous _croyez_ testés ne s'exécutent peut-être jamais. Et comme le note Meszaros, les erreurs de codage sont plus faciles à cacher dans un test obscur, produisant des Tests bogués qui passent pour de mauvaises raisons.

## Treatment

Faites en sorte que chaque test raconte une histoire autonome dont l'intention est visible dans le corps du test.

1. **Vérifiez une seule condition par test.** Scindez un Test avide en tests ciblés, chacun nommé d'après le comportement qu'il vérifie. Cela corrige aussi le masquage de couverture.
2. **Intégrez en ligne la fixture pertinente (éliminez l'Invité mystère).** Construisez les données dont le test dépend _dans le test_, ou via une méthode de création/builder explicite et nommée — ne chargez pas de fichiers anonymes ni de lignes de BD partagées.
3. **Utilisez une fixture minimale / fraîche.** Ne construisez que ce dont ce test a besoin ; remplacez un large `beforeEach` partagé par un builder qui fixe par défaut le bruit et laisse chaque test ne définir que le champ testé.
4. **Nommez vos données.** Remplacez les littéraux magiques par des constantes/variables au nom révélateur d'intention pour que l'assertion s'explique d'elle-même.
5. **Assertez le sens, pas tout.** Préférez des assertions ciblées à un snapshot géant ; si vous faites un snapshot, gardez-le petit et relisible pour que les faits pertinents ne soient pas noyés dans une sortie non pertinente.
6. **Cachez la mécanique, pas l'intention.** Repoussez le câblage accessoire dans des méthodes utilitaires/d'aide bien nommées afin que le corps du test se lise comme configuration → action → attente.

```js
// Avant (obscur)
test('user', async () => {
  const data = loadFixture('users.json');
  const svc = new UserService(data, cfg, clock);
  const u = await svc.create({ ...data[0], roles: ['a','b'], flags: 0x1f });
  expect(await svc.count()).toBe(data.length + 1);
});

// Après : une seule intention, fixture en ligne, données nommées
test('create() persists a new user', async () => {
  const svc = userServiceWith([]);                 // fixture minimale et fraîche
  const newUser = aUser({ email: 'ada@example.com' }); // le builder fixe par défaut le bruit

  const created = await svc.create(newUser);

  expect(created.email).toBe('ada@example.com');
  expect(await svc.count()).toBe(1);               // explicite, aucun fichier externe
});

```

Visez la forme AAA (Arrange-Act-Assert / Préparer-Agir-Vérifier) : un lecteur devrait saisir le scénario sans quitter la méthode de test.

## Detected by

- **eslint-plugin-jest** `max-expects` — jest/max-expects (https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/max-expects.md)
- **eslint-plugin-vitest** `max-expects` — vitest/max-expects (https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/max-expects.md)
- **eslint-plugin-jest** `no-large-snapshots` — jest/no-large-snapshots (https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/no-large-snapshots.md)
- **eslint-plugin-vitest** `no-large-snapshots` — vitest/no-large-snapshots (https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-large-snapshots.md)
- **eslint-plugin-jest** `max-nested-describe` — jest/max-nested-describe (https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/max-nested-describe.md)
