---
title: "Initialisation dans le constructeur"
type: "test-smell"
slug: "constructor-initialization"
url: "http://localhost:3000/fr/test-smells/constructor-initialization.md"
category: "Odeurs de fixture"
description: "Une classe de test initialise les champs de sa fixture dans un constructeur au lieu du hook de mise en place dédié du framework (setUp / @BeforeEach / TestInitialize), contournant le cycle de vie des tests."
---
# Initialisation dans le constructeur

> Une classe de test initialise les champs de sa fixture dans un constructeur au lieu du hook de mise en place dédié du framework (setUp / @BeforeEach / TestInitialize), contournant le cycle de vie des tests.

## Signs and Symptoms

Une classe de test possède un constructeur explicite qui construit le système sous test, les mocks ou les champs partagés — un travail qui revient au hook de mise en place du framework (`setUp()` / `@Before` / `@BeforeEach` dans JUnit, `[TestInitialize]` dans MSTest, `beforeEach` dans Jest/Vitest).

Signes révélateurs :

* La classe de test déclare un constructeur qui affecte des champs `final`/d’instance.
* La mise en place de la fixture vit en dehors du cycle de vie réellement géré par le framework — de sorte que `@BeforeEach`/`@AfterEach`, la mise en place de la classe de base et la réinitialisation par test sont silencieusement contournés ou exécutés dans un ordre déroutant par rapport au constructeur.
* La mise en place asynchrone est impossible : vous ne pouvez pas faire `await` dans un constructeur, si bien que le travail de fixture asynchrone est forcé au mauvais endroit ou dupliqué.

Forme JUnit canonique (tirée du catalogue des odeurs de test) :

```java
public class TagEncodingTest extends BrambleTestCase {
    private final CryptoComponent crypto;
    private final SecretKey tagKey;

    public TagEncodingTest() {                 // odeur : initialisation dans le constructeur
        crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
        tagKey = TestUtils.getSecretKey();
    }
}

```

Équivalent JS/TS — fixture construite une seule fois à la portée `describe`/module (l’équivalent du constructeur) au lieu de par test :

```ts
describe('Cart', () => {
  const cart = new Cart();          // construite une seule fois, fait fuir l'état entre les tests
  it('adds an item', () => { cart.add(apple); expect(cart.size).toBe(1); });
  it('starts empty', () => { expect(cart.size).toBe(0); }); // échoue : voit apple
});

```

## Reasons for the Problem

**Pourquoi cela arrive**

* Les développeurs qui ne connaissent pas le rôle du hook de mise en place se tournent vers la fonctionnalité du langage qu’ils maîtrisent déjà — le constructeur — pour initialiser les champs.
* Cela paraît plus propre : des champs `final` assortis d’un constructeur semblent idiomatiques, et un IDE peut même générer le constructeur à votre place.
* Du cargo-culting depuis les classes de production, où l’initialisation dans le constructeur _est_ le bon modèle.

**Pourquoi c’est nuisible**

* **Contourne le cycle de vie des tests.** Les frameworks promettent un état neuf pour chaque test via le hook de mise en place et une paire `@BeforeEach`/`@AfterEach` (ou `beforeEach`/`afterEach`). La logique cachée dans un constructeur se situe en dehors de ce contrat, si bien que le nettoyage, la mise en place de la classe de base et les garanties d’ordonnancement peuvent ne pas s’appliquer comme prévu.
* **Fuite d’état et instabilité.** Dans les frameworks qui réutilisent une seule instance (ou lorsque vous stockez la fixture dans une variable partagée/de portée `describe`), les mutations d’un test débordent sur le suivant. Les tests réussissent ou échouent selon l’ordre d’exécution — fausse confiance et échecs intermittents.
* **Pas de mise en place asynchrone.** Un constructeur ne peut pas faire `await`. Toute mise en place nécessitant des E/S, une connexion à une base de données ou un serveur démarré ne peut pas y résider proprement ; on contourne le problème avec des appels bloquants ou du code dupliqué par test.
* **Lisibilité/cohérence.** Les relecteurs et l’outillage s’attendent à trouver les fixtures dans le hook conventionnel. Répartir l’initialisation entre un constructeur et une méthode de mise en place rend la vraie fixture difficile à trouver et facile à mal configurer.

**Mise en garde — cela dépend du framework.** Ce n’est pas une règle universelle. Dans **xUnit.net**, le constructeur _est_ la mise en place idiomatique par test (associé à `IDisposable` pour le nettoyage), et MSTest propose même une règle inverse et optionnelle (`MSTEST0020`) qui privilégie les constructeurs plutôt que `[TestInitialize]`. Considérez « Initialisation dans le constructeur » comme une odeur spécifiquement là où votre framework fournit un hook de mise en place dédié (JUnit, MSTest classique, NUnit) et que vous le contournez.

## Treatment

Déplacez l’initialisation des champs hors du constructeur, vers le hook de mise en place du framework, et préférez un état neuf par test plutôt que des instances partagées.

1. Supprimez le constructeur de la classe de test ; déclarez les champs et affectez-les dans le hook de mise en place (`@BeforeEach`/`setUp()` pour JUnit, `[TestInitialize]` pour MSTest, `beforeEach` pour Jest/Vitest).
2. Si la mise en place est asynchrone, c’est obligatoire — le hook peut faire `await` ; un constructeur, non.
3. En JS/TS, construisez la fixture _à l’intérieur_ de `beforeEach` (en réaffectant un `let`) plutôt qu’une seule fois à la portée `describe`/module, afin que chaque test obtienne un objet propre.
4. Gardez un nettoyage symétrique : associez `@BeforeEach` à `@AfterEach` (ou, dans le style à constructeur de xUnit.net, implémentez `IDisposable`).

Avant → après (JUnit) :

```java
// avant
public class TagEncodingTest {
    private final CryptoComponent crypto = new CryptoComponentImpl(...);
}

// après
public class TagEncodingTest {
    private CryptoComponent crypto;

    @BeforeEach
    void setUp() {
        crypto = new CryptoComponentImpl(...);
    }
}

```

Avant → après (Jest/Vitest) :

```ts
describe('Cart', () => {
  let cart: Cart;
  beforeEach(() => { cart = new Cart(); }); // neuf pour chaque test
  it('adds an item', () => { cart.add(apple); expect(cart.size).toBe(1); });
  it('starts empty', () => { expect(cart.size).toBe(0); });
});

```

Si la fixture est véritablement immuable et coûteuse (et que votre framework le permet), un hook unique tel que `@BeforeAll`/`beforeAll` est acceptable — mais uniquement pour un état partagé véritablement en lecture seule, jamais pour des objets que les tests modifient.

## Detected by

- **tsDetect (Test Smell Detector)** `Constructor Initialization` (https://testsmells.org/pages/testsmells.html)
- **MSTest Roslyn analyzers** `MSTEST0019` (https://learn.microsoft.com/en-us/dotnet/core/testing/mstest-analyzers/mstest0019)
