Gestion de l'état : beforeEach, afterEach et nettoyage des ressources

Puisque les tests sont lancés les uns après les autres dans le cadre d'un seul processus Node.js, ils peuvent influencer involontairement les uns les autres. Si le test n°1 a écrit quelque chose dans une variable globale ou a changé l'état d'un mock, le test n°2 peut échouer à cause de cet état « sale ».

Pour assurer une propreté et une isolation idéales, chaque cas de test doit être lancé dans un environnement vierge. Pour cela, on utilise des .

1. Quatre hooks de cycle de vie dans Vitest

Les frameworks de test fournissent quatre outils principaux pour lancer automatiquement du code avant et après les tests :

  • beforeEach(fn) : Lance la fonction avant chaque test dans le bloc describe actuel. Idéal pour réinitialiser l'état ou recréer des objets.
  • afterEach(fn) : Lance la fonction après chaque test. Généralement utilisé pour nettoyer les mocks, fermer les connexions ou réinitialiser les changements DOM.
  • beforeAll(fn) : Lance la fonction une fois avant tous les tests du fichier. Convient pour les travaux de préparation lourds (par exemple, démarrage d'une base de données de test en mémoire).
  • afterAll(fn) : Lance la fonction une fois après tous les tests du fichier. Convient pour le nettoyage final (par exemple, fermeture des connexions réseau).
beforeAll  ---> [ beforeEach -> TEST 1 -> afterEach ] ---> [ beforeEach -> TEST 2 -> afterEach ] ---> afterAll

2. Problème d'accumulation d'historique dans les mocks

Puisque les mocks mémorisent l'historique des appels, les compteurs d'appels s'accumulent. Regardez ce problème :

const myMock = vi.fn();

test('Premier test', () => {
    myMock();
    expect(myMock).toHaveBeenCalledTimes(1); // Passe !
});

test('Deuxième test', () => {
    myMock();
    expect(myMock).toHaveBeenCalledTimes(1); // ERREUR ! Le compteur est égal à 2 !
});

3. Méthodes de nettoyage des mocks

Pour résoudre ce problème, il est nécessaire de nettoyer les mocks entre les tests. Pour cela, on appelle des méthodes spéciales dans afterEach ou beforeEach :

  • mockClear() / vi.clearAllMocks() : Réinitialise l'historique des appels du mock (compteur d'appels et arguments passés). La valeur retournée par le mock est conservée.
  • mockReset() / vi.resetAllMocks() : Réinitialise l'historique des appels et nettoie le comportement configuré (maintenant le mock retourne undefined).
  • mockRestore() / vi.restoreAllMocks() : Restaure complètement l'implémentation originale de la fonction à la place de l'espion. Cela fonctionne seulement pour les espions créés via vi.spyOn().

Pattern idéal de configuration des fichiers de tests :

import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';

describe('Module de paiement', () => {
    let paymentService;
    const mockApi = vi.fn();

    beforeEach(() => {
        // Préparation de données propres avant chaque test
        paymentService = new PaymentService(mockApi);
    });

    afterEach(() => {
        // Nettoyage de l'historique des appels des mocks après chaque test
        vi.clearAllMocks();
    });

    test('paiement réussi', () => {
        mockApi.mockReturnValue({ success: true });
        paymentService.pay(100);
        expect(mockApi).toHaveBeenCalledTimes(1);
    });

    test('paiement rejeté', () => {
        mockApi.mockReturnValue({ success: false });
        paymentService.pay(200);
        expect(mockApi).toHaveBeenCalledTimes(1); // Passe, car vi.clearAllMocks() a réinitialisé le compteur !
    });
});
Astuce

Vous pouvez configurer la réinitialisation globale des mocks dans le fichier de configuration vitest.config.js avec les paramètres clearMocks: true ou restoreMocks: true. Cela vous évitera d'avoir à écrire le nettoyage manuellement dans chaque fichier de test !