Vitest : mocks, espions et temps
Un mock remplace une dépendance (réseau, base de données, horloge) pour rendre un test rapide, déterministe et isolé. L’objet vi fournit les outils.
Fonctions simulées : vi.fn
Section intitulée « Fonctions simulées : vi.fn »import { expect, it, vi } from 'vitest';
it('appelle le rappel avec le résultat', () => { const rappel = vi.fn(); traiter([1, 2, 3], rappel);
expect(rappel).toHaveBeenCalledTimes(3); expect(rappel).toHaveBeenCalledWith(2); expect(rappel).toHaveBeenNthCalledWith(1, 1); expect(rappel.mock.calls).toEqual([[1], [2], [3]]);});
it('simule des valeurs de retour', async () => { const obtenir = vi.fn().mockReturnValueOnce('a').mockReturnValue('b'); expect(obtenir()).toBe('a'); expect(obtenir()).toBe('b');
const charger = vi.fn().mockResolvedValue({ id: 1 }); await expect(charger()).resolves.toEqual({ id: 1 });
const echouer = vi.fn().mockRejectedValue(new Error('réseau')); await expect(echouer()).rejects.toThrow('réseau');});| Méthode | Rôle |
|---|---|
mockReturnValue(v), mockReturnValueOnce(v) |
Valeur renvoyée (toujours, ou une seule fois) |
mockResolvedValue(v), mockRejectedValue(e) |
Promesse résolue ou rejetée |
mockImplementation(fn) |
Remplace le corps |
mockClear() |
Vide l’historique des appels |
mockReset() |
Vide l’historique et les comportements |
mockRestore() |
Rétablit la fonction d’origine (avec spyOn) |
Espionner : vi.spyOn
Section intitulée « Espionner : vi.spyOn »Observer (ou remplacer) une méthode existante :
import { afterEach, expect, it, vi } from 'vitest';
afterEach(() => vi.restoreAllMocks());
it('journalise une erreur', () => { const erreur = vi.spyOn(console, 'error').mockImplementation(() => {}); traiterEntreeInvalide(); expect(erreur).toHaveBeenCalledWith(expect.stringContaining('invalide'));});
it('observe sans remplacer', () => { const espion = vi.spyOn(calcul, 'additionner'); // appelle la vraie fonction calcul.additionner(1, 2); expect(espion).toHaveBeenCalledWith(1, 2);});Penser à restaurer (vi.restoreAllMocks() ou l’option restoreMocks: true de la configuration) : un espion oublié fausse les tests suivants.
Remplacer un module : vi.mock
Section intitulée « Remplacer un module : vi.mock »import { expect, it, vi } from 'vitest';import { chargerClient } from './api';import { afficherNom } from './affichage';
vi.mock('./api'); // toutes les exports deviennent des fonctions simulées
it('affiche le nom du client', async () => { vi.mocked(chargerClient).mockResolvedValue({ id: 1, nom: 'Alice' }); expect(await afficherNom(1)).toBe('Alice');});Avec une implémentation de remplacement :
vi.mock('./horloge', () => ({ maintenant: () => new Date('2026-01-15T10:00:00Z'),}));| Point | Détail |
|---|---|
vi.mock est remonté en haut du fichier |
Il s’applique avant les imports, même écrit plus bas |
| Variables utilisées dans la fabrique | Les déclarer avec vi.hoisted(...) |
| Garder une partie du module réel | const reel = await vi.importActual('./module') dans la fabrique |
| Modules Node.js | vi.mock('node:fs') (souvent préférable : tester avec un dossier temporaire) |
const { envoyer } = vi.hoisted(() => ({ envoyer: vi.fn() }));vi.mock('./courriel', () => ({ envoyer }));Ne pas abuser des mocks de modules : un test qui simule tout ne vérifie plus rien. Préférer injecter les dépendances en paramètre (fonctions, classes) : on passe alors un faux sans vi.mock.
Simuler fetch
Section intitulée « Simuler fetch »import { afterEach, beforeEach, expect, it, vi } from 'vitest';
beforeEach(() => { vi.stubGlobal('fetch', vi.fn());});afterEach(() => { vi.unstubAllGlobals();});
it('traite une réponse JSON', async () => { vi.mocked(fetch).mockResolvedValue( new Response(JSON.stringify([{ id: 1, nom: 'Alice' }]), { status: 200, headers: { 'Content-Type': 'application/json' }, }), ); const clients = await listerClients(); expect(clients).toHaveLength(1); expect(fetch).toHaveBeenCalledWith('/api/clients', expect.anything());});
it('traite une erreur serveur', async () => { vi.mocked(fetch).mockResolvedValue(new Response('erreur', { status: 500 })); await expect(listerClients()).rejects.toThrow('HTTP 500');});Pour des scénarios réseau plus riches, la bibliothèque MSW (Mock Service Worker) intercepte les requêtes au niveau du réseau et sert de serveur factice partagé entre tests.
Variables d’environnement et globales
Section intitulée « Variables d’environnement et globales »it("lit l'adresse de l'API", () => { vi.stubEnv('API_URL', 'https://api.test'); expect(construireUrl('/clients')).toBe('https://api.test/clients');});
afterEach(() => vi.unstubAllEnvs());vi.stubEnv modifie process.env et import.meta.env pour la durée du test ; vi.unstubAllEnvs() restaure.
Simuler le temps
Section intitulée « Simuler le temps »import { afterEach, beforeEach, expect, it, vi } from 'vitest';
beforeEach(() => { vi.useFakeTimers(); vi.setSystemTime(new Date('2026-01-15T10:00:00Z'));});afterEach(() => { vi.useRealTimers();});
it('expire après 30 secondes', () => { const session = creerSession(); expect(session.active).toBe(true); vi.advanceTimersByTime(30_000); expect(session.active).toBe(false);});
it('utilise la date simulée', () => { expect(new Date().toISOString()).toBe('2026-01-15T10:00:00.000Z');});
it('exécute les minuteurs asynchrones', async () => { const promesse = attendreEtRepondre(); // utilise setTimeout await vi.advanceTimersByTimeAsync(1000); await expect(promesse).resolves.toBe('ok');});| Méthode | Rôle |
|---|---|
useFakeTimers() |
Remplace setTimeout, setInterval, Date |
setSystemTime(date) |
Fixe la date courante |
advanceTimersByTime(ms) |
Avance l’horloge |
advanceTimersByTimeAsync(ms) |
Idem, en laissant les promesses se résoudre |
runAllTimers() |
Exécute tous les minuteurs en attente |
useRealTimers() |
Rétablit l’horloge réelle |
Toujours rétablir l’horloge réelle après le test : des minuteurs simulés oubliés bloquent les tests suivants.
Configuration utile
Section intitulée « Configuration utile »export default defineConfig({ test: { clearMocks: true, // vide l'historique des appels avant chaque test restoreMocks: true, // restaure les espions unstubEnvs: true, // restaure les variables d'environnement simulées unstubGlobals: true, // restaure les globales simulées },});Ces options évitent la plupart des fuites d’un test à l’autre.
Quand ne pas mocker
Section intitulée « Quand ne pas mocker »| Cas | Préférer |
|---|---|
| Base de données | Une base réelle jetable (SQLite en mémoire, conteneur) pour les tests d’intégration |
| Système de fichiers | Un dossier temporaire (fs.mkdtemp) |
| Logique pure | Aucune simulation nécessaire |
| Code que vous ne maîtrisez pas | Tester le contrat avec un faux simple |
Dépannage
Section intitulée « Dépannage »| Symptôme | Piste |
|---|---|
vi.mock ne remplace rien |
Chemin différent de celui importé par le code testé (comparer les chemins résolus) |
ReferenceError dans la fabrique de vi.mock |
Variable non « hoisted » : vi.hoisted |
| Un espion influence les tests suivants | restoreMocks: true ou vi.restoreAllMocks() |
| Le test se bloque avec les minuteurs simulés | Promesses en attente : advanceTimersByTimeAsync |
fetch is not a function |
Environnement ou version de Node.js ancienne : vi.stubGlobal('fetch', …) |
| Les appels du test précédent sont comptés | clearMocks: true |
Ubuntu 24.04 et 26.04 : différences à connaître
Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »Aucune.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Mocking, API
vi - Suite : Composants et DOM