Tests unitaires Vitest : de la configuration au workflow TDD

Combien de temps faut-il pour configurer Jest sur un projet ESM ? ts-jest, babel-jest, jest.config.js… et toute une série de problèmes de résolution de modules. J’ai déjà passé un après-midi entier rien que pour que Jest reconnaisse correctement les import de fichiers .vue.
Avec Vitest, une ligne de configuration suffit.
Ce n’est pas une exagération. La première fois que j’ai lancé vitest dans un projet Vite, la suite s’est exécutée en quelques secondes — comme remplacer un PC lent de trois ans : tout devient fluide.
Vitest est-il vraiment si rapide ? Les données officielles et les benchmarks communautaires convergent : démarrage à froid ~200 ms (Jest : 2-4 s), ~500 cas de test en ~8 s (Jest : ~45 s). Il partage la config Vite, supporte TypeScript nativement, et l’API ressemble à Jest — la migration peut tenir en une demi-heure.
Cet article vous mène de la configuration initiale au workflow TDD complet, avec mocking et couverture. Nouveau projet ou migration depuis Jest, vous y trouverez ce qu’il vous faut.
Qu’est-ce que Vitest ? Pourquoi est-il si rapide ?
En bref, Vitest est le framework de tests natif de Vite.
Si vous construisez déjà avec Vite, Vitest est prêt à l’emploi. Il réutilise la config Vite — alias, variables d’environnement, traitement CSS — sans empiler transform, moduleFileExtensions et moduleNameMapper comme avec Jest.
Trois atouts principaux :
Vitesse. Démarrage à froid ~200 ms ; Jest met souvent 2-4 s. Sur un gros projet : ~500 tests en ~8 s (Vitest) contre ~45 s (Jest), d’après des mesures DEV Community en 2026. L’écart est net.
Compatibilité Jest. API quasi identique — describe, it, expect, vi.fn() — la migration se résume souvent à changer les chemins d’import.
Watch intelligent. La fonction « HMR for tests » ne relance que les tests impactés, pas toute la suite. Le retour immédiat en développement change la donne.
Passons à l’installation.
Installation et configuration
Prérequis : Vite >= 6.0.0, Node >= 20.0.0. Sur un projet récent, c’est en général déjà le cas.
Installation
Une commande :
npm install -D vitest
C’est tout. Pas besoin de @types/jest, ts-jest, jest-environment-jsdom… Vitest gère TypeScript nativement.
Fichier de configuration
Deux options : ajouter test dans vite.config.ts, ou créer un vitest.config.ts dédié.
Pour un projet simple, vite.config.ts suffit :
// vite.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: true, // describe, it, expect globaux sans import
environment: 'node', // ou 'jsdom' pour le navigateur
include: ['tests/**/*.test.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
},
},
})
globals: true est pratique : describe, it, expect deviennent globaux, comme avec Jest.
Pour les API DOM (composants), passez environment à 'jsdom' et installez jsdom :
npm install -D jsdom
Scripts npm
Dans package.json :
{
"scripts": {
"test": "vitest",
"test:run": "vitest run"
}
}
npm test lance le watch ; npm run test:run une exécution unique (CI).
La config tient en peu de lignes — bien moins que les preset, transform et moduleFileExtensions de Jest.
Écrire des tests unitaires
Convention : .test.ts ou .spec.ts, dans tests/ ou à côté des sources — selon l’équipe.
Structure de base
Un test minimal :
import { describe, it, expect } from 'vitest'
import { add, divide } from './math'
describe('Math utilities', () => {
it('should add two numbers', () => {
expect(add(2, 3)).toBe(5)
})
it('should throw on division by zero', () => {
expect(() => divide(10, 0)).toThrow('Division by zero')
})
})
describe groupe les tests, it définit un cas, expect assert.
Avec globals: true, l’import peut être omis.
Assertions courantes
Les plus utilisées :
// Égalité
expect(value).toBe(5) // stricte (===)
expect(obj).toEqual({ a: 1 }) // profonde
// Vérité
expect(value).toBeTruthy()
expect(value).toBeFalsy()
expect(value).toBeNull()
// Exceptions
expect(() => fn()).toThrow()
expect(() => fn()).toThrow('Error message')
// Nombres
expect(n).toBeGreaterThan(10)
expect(n).toBeLessThanOrEqual(5)
// Tableaux / chaînes
expect(arr).toContain('item')
expect(str).toMatch(/pattern/)
Filtrer les tests
Pour n’exécuter qu’un test : .only :
it.only('n\'exécute que celui-ci', () => { ... })
Pour ignorer temporairement : .skip :
it.skip('ignoré pour l\'instant', () => { ... })
Même chose pour describe : describe.only(...), describe.skip(...).
En mode watch, modifier le code fait passer les tests au vert ou au rouge immédiatement — bien plus agréable que d’attendre plusieurs secondes à chaque run Jest.
TDD en pratique
Le TDD (test-driven development), c’est simple : tests d’abord, code ensuite.
Ça paraît contre-intuitif, mais ça force à clarifier ce que la fonction doit faire avant d’implémenter — plutôt que d’ajouter des tests après coup pour « faire le quota » de couverture.
Exemple complet : une fonction validateEmail.
Étape 1 : tests sans implémentation
Créez le fichier de test :
// tests/validateEmail.test.ts
import { describe, it, expect } from 'vitest'
import { validateEmail } from '../src/validateEmail'
describe('validateEmail', () => {
it('should return true for valid email', () => {
expect(validateEmail('[email protected]')).toBe(true)
})
it('should return false for invalid email', () => {
expect(validateEmail('invalid')).toBe(false)
})
})
La fonction n’existe pas encore : les tests échouent. C’est la première étape du TDD.
Étape 2 : code minimal
Implémentation minimale pour passer :
// src/validateEmail.ts
export function validateEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}
Lancez npm test. Les deux tests passent ? Étape 2 terminée.
Étape 3 : tests de bord
Ajoutez des cas limites :
// tests/validateEmail.test.ts (suite)
it('should return false for empty string', () => {
expect(validateEmail('')).toBe(false)
})
it('should return false for email without domain', () => {
expect(validateEmail('user@')).toBe(false)
})
it('should return false for email with spaces', () => {
expect(validateEmail('test @example.com')).toBe(false)
})
Si tout passe, la regex tient la route ; sinon, corrigez-la.
Étape 4 : refactor
Les tests passent : vous pouvez refactoriser — regex plus stricte, constantes, etc. :
// src/validateEmail.ts
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
export function validateEmail(email: string): boolean {
if (!email || email.trim() === '') {
return false
}
return EMAIL_REGEX.test(email)
}
Relancez les tests : toujours verts. Le TDD protège le refactor.
Pourquoi le TDD aide
Au début, « tests d’abord » déroute. Après quelques cycles :
- Réfléchir avant de coder : le test décrit le comportement attendu.
- Itération rapide : le watch Vitest répond en secondes.
- Refactor en confiance : la suite signale toute régression.
Commencez par des utilitaires simples (formatage, helpers), puis montez en complexité.
Mocking avancé
Les tests unitaires isolent souvent les dépendances — API, base de données, libs tierces. D’où les mocks.
Vitest propose une API proche de Jest, centrée sur vi.
vi.fn() : mocker une fonction
Créer une fausse fonction :
import { vi, describe, it, expect } from 'vitest'
describe('vi.fn() demo', () => {
it('tracks calls', () => {
const mockFn = vi.fn()
mockFn('hello')
mockFn('world')
expect(mockFn).toHaveBeenCalledTimes(2)
expect(mockFn).toHaveBeenNthCalledWith(1, 'hello')
})
})
Valeur de retour prédéfinie :
const mockFn = vi.fn().mockReturnValue('mocked result')
// ou asynchrone
const asyncMock = vi.fn().mockResolvedValue({ data: 'ok' })
vi.mock() : mocker un module
Pour une fonction qui appelle une API externe :
import { vi, describe, it, expect, beforeEach } from 'vitest'
import { fetchUser } from './api'
import { UserService } from './UserService'
vi.mock('./api', () => ({
fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'Alice' })
}))
describe('UserService', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('should fetch user', async () => {
const service = new UserService()
const user = await service.getUser(1)
expect(user.name).toBe('Alice')
expect(fetchUser).toHaveBeenCalledWith(1)
})
})
vi.mock() s’exécute avant l’import du module — placez-le en tête de fichier.
vi.spyOn() : espionner sans remplacer
Pour enregistrer les appels sans casser l’implémentation :
import { vi, describe, it, expect, afterEach } from 'vitest'
import { calculator } from './calculator'
describe('spyOn demo', () => {
afterEach(() => {
vi.restoreAllMocks()
})
it('tracks add calls', () => {
const addSpy = vi.spyOn(calculator, 'add')
const result = calculator.add(2, 3)
expect(result).toBe(5)
expect(addSpy).toHaveBeenCalledWith(2, 3)
})
})
spyOn est plus doux que mock : la fonction réelle tourne, avec un « enregistreur » en plus.
Mocker les objets globaux
Sans vraie requête HTTP :
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ data: 'mocked' })
}))
vi.stubGlobal s’applique aussi à window, localStorage, etc.
Le mocking est la partie la plus délicate. Commencez par vi.fn(), puis vi.mock(). Nettoyez entre les tests (clearAllMocks ou restoreAllMocks) pour éviter la pollution entre cas.
Couverture et bonnes pratiques
La couverture mesure une partie de la qualité. Vitest propose v8 (rapide, natif) et istanbul (compatibilité). En pratique, v8 suffit.
Configurer la couverture
Dans vite.config.ts :
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
thresholds: {
lines: 80,
functions: 80,
branches: 70,
},
exclude: ['node_modules/', 'tests/', '**/*.d.ts'],
},
}
Commande :
vitest run --coverage
Le terminal affiche le rapport ; le dossier coverage/ contient la version HTML des lignes non couvertes.
Intérêt des seuils
Si la couverture est sous le seuil, Vitest échoue — utile en CI pour bloquer les commits trop légers. 80 % est un bon point de départ ; viser 100 % épuise souvent l’équipe.
Intégration CI/CD
Dans GitHub Actions ou autre CI :
# .github/workflows/test.yml
- name: Run tests with coverage
run: npm run test:run -- --coverage
Uploadez le rapport lcov vers Codecov ou Coveralls pour suivre l’évolution.
Quelques conseils
- Pas de quête du 100 % : un chiffre élevé ne garantit pas la qualité. Testez la logique critique.
- Chemins principaux d’abord : happy path prioritaire, branches d’erreur ensuite.
- Nettoyer les tests obsolètes : la suite se maintient comme le code.
- Garder le watch actif :
vitesten écoute pendant le dev pour détecter tout de suite.
Synthèse
Vitest, en trois mots : rapide, simple, efficace.
Rapide — ~200 ms à froid, ~500 tests en ~8 s, un ordre de grandeur devant Jest. Simple — config partagée avec Vite, peu de réglages. Efficace — API proche de Jest, migration légère.
Projet Vite ? Vitest est le choix naturel ; inutile de se battre avec l’ESM sur Jest.
Encore sur Jest ? Essayez une migration en une demi-heure : installez Vitest, changez les imports — souvent ça tourne directement.
Pour le TDD, commencez petit : utilitaires, tests d’abord. Vous verrez que « réfléchir avant de coder » accélère souvent le travail.
Les tests ne sont pas une corvée, c’est une assurance. Configurez Vitest correctement, et vous coderez plus sereinement.
Configuration Vitest et workflow TDD
Étapes complètes pour configurer Vitest et pratiquer le développement piloté par les tests
⏱️ Estimated time: 30 min
- 1
Step 1: Installer Vitest
Exécutez la commande d'installation :
```bash
npm install -D vitest
```
Prérequis : Vite >= 6.0.0, Node >= 20.0.0 - 2
Step 2: Configurer vite.config.ts
Ajoutez le champ test dans la configuration :
```typescript
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['tests/**/*.test.ts'],
},
})
```
globals: true évite d'importer describe, it et expect à chaque fichier. - 3
Step 3: Ajouter les scripts npm
Dans package.json :
```json
{
"scripts": {
"test": "vitest",
"test:run": "vitest run"
}
}
```
npm test lance le mode watch ; npm run test:run une exécution unique (CI). - 4
Step 4: Écrire le premier test
Créez tests/math.test.ts :
```typescript
import { describe, it, expect } from 'vitest'
describe('Math', () => {
it('should add numbers', () => {
expect(1 + 1).toBe(2)
})
})
```
Lancez npm test pour valider la configuration. - 5
Step 5: Pratiquer le workflow TDD
Suivez le développement piloté par les tests :
• Étape 1 : écrire le test et définir le comportement attendu
• Étape 2 : implémenter le minimum pour passer
• Étape 3 : ajouter les tests de bord
• Étape 4 : refactoriser
Le mode watch Vitest offre un retour en quelques secondes. - 6
Step 6: Configurer la couverture
Ajoutez la configuration coverage :
```typescript
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
thresholds: {
lines: 80,
functions: 80,
},
}
```
Exécutez vitest run --coverage pour générer le rapport.
FAQ
Quelle différence entre Vitest et Jest ?
Comment migrer de Jest vers Vitest ?
• Désinstallez les paquets Jest, installez vitest
• Migrez jest.config.js vers vite.config.ts
• Remplacez import from 'jest' par from 'vitest'
• Remplacez jest.fn() et jest.mock() par vi.fn() et vi.mock()
En général, 30 minutes suffisent.
Quels environnements de test Vitest prend-il en charge ?
Comment mocker les requêtes API dans Vitest ?
• vi.fn() : mocker une fonction avec une valeur de retour prédéfinie
• vi.mock() : mocker un module entier et remplacer ses exports
• vi.spyOn() : surveiller les appels sans remplacer l'implémentation
Nettoyez avec vi.clearAllMocks() ou vi.restoreAllMocks() après chaque test.
Comment configurer la couverture Vitest ?
Quel est le cœur du workflow TDD ?
• Rouge : écrire d'abord un test qui échoue
• Vert : écrire le minimum de code pour passer
• Refactor : améliorer la structure
Avec le mode watch Vitest, le retour est quasi instantané. Écrire les tests en premier clarifie la conception des fonctions.
8 min de lecture · Publié le: 14 avr. 2026 · Mis à jour le: 27 juil. 2026
Guide de test Vitest
Vous lisez le premier article de cette série. Continuez avec le suivant ou ouvrez le hub de la série pour voir tout le parcours.
Précédent
Vous êtes au début de cette série.
Suivant
Tests unitaires Vitest : TDD et configuration de la couverture
Tutoriel TDD avec Vitest : cycle Red-Green-Refactor, rapports de couverture et seuils CI, vi.fn/vi.spy/vi.mock expliqués — le framework de tests frontend de référence en 2026.
Partie 2 sur 3



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire