Vitest en 2026 : Le Nouveau Standard des Tests en JavaScript Moderne
Salut HaWkers, si vous utilisez encore Jest dans vos nouveaux projets, vous perdez probablement du temps et des performances. Vitest s'est imposé comme le framework de tests standard de l'écosystème JavaScript moderne en 2026.
Comprenons pourquoi ce changement s'est produit et comment vous pouvez en bénéficier.
Pourquoi Vitest a Gagné
Le Contexte du Changement
// Timeline du passage de Jest à Vitest
const testingTimeline = {
2020: 'Jest est le standard incontesté',
2022: 'Vitest 0.x émerge avec Vite',
2023: 'Vitest 1.0 - prêt pour la production',
2024: {
vitest: 'Adoption massive dans les nouveaux projets',
jest: 'Encore dominant dans le legacy'
},
2025: {
vitest4: 'Vitest 4.0 stable',
angular: 'Angular 21 adopte Vitest par défaut',
breaking: 'Point de bascule'
},
2026: {
status: 'Vitest est le nouveau standard',
jest: 'Maintenu pour le legacy',
newProjects: '80%+ utilisent Vitest'
}
};Avantages de Vitest
// Pourquoi Vitest a surpassé Jest
const vitestAdvantages = {
// 1. Performance
performance: {
speedup: '2-10x plus rapide que Jest',
why: 'Réutilise le pipeline de Vite',
watch: 'Watch mode instantané',
benchmark: `
Projet moyen (500 tests) :
Jest : 45 secondes
Vitest : 8 secondes
`
},
// 2. Configuration zéro avec Vite
zeroConfig: {
if: 'Vous utilisez Vite',
then: 'Vitest fonctionne sans config',
shared: 'Même config de plugins, aliases, etc',
dx: 'Pas besoin de maintenir deux configs'
},
// 3. ESM natif
esm: {
jest: 'ESM est un add-on, casse souvent',
vitest: 'ESM est natif, fonctionne parfaitement',
impact: 'Peut tester du code ESM-only sans hacks'
},
// 4. TypeScript natif
typescript: {
jest: 'Nécessite ts-jest ou babel',
vitest: 'TypeScript fonctionne out of the box',
types: 'Types inclus, pas de @types/vitest'
},
// 5. API compatible avec Jest
compatibility: {
api: 'describe, it, expect - même API',
migration: 'La plupart des tests fonctionnent sans changement',
mocks: 'vi.mock() similaire à jest.mock()'
},
// 6. Features modernes
modernFeatures: {
browserMode: 'Teste dans un vrai browser (Playwright)',
snapshotInline: 'Snapshots inline dans le code',
coverage: 'v8 coverage natif',
ui: 'UI interactive intégrée'
}
};
Commencer avec Vitest
Setup Basique
// Installation et configuration
// 1. Installer
// npm install -D vitest
// 2. Ajouter script dans package.json
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage"
}
}
// 3. Configuration (optionnel - fonctionne sans config avec Vite)
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
// Globals (describe, it, expect) sans import
globals: true,
// Environnement de test
environment: 'jsdom', // ou 'node', 'happy-dom'
// Fichiers de setup
setupFiles: ['./tests/setup.ts'],
// Coverage
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
exclude: ['node_modules/', 'tests/']
},
// Inclure/exclure des fichiers
include: ['**/*.{test,spec}.{ts,tsx}'],
},
});Écrire des Tests
// Syntaxe basique - très similaire à Jest
// math.test.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { sum, multiply } from './math';
describe('Math functions', () => {
it('should sum two numbers', () => {
expect(sum(1, 2)).toBe(3);
});
it('should multiply two numbers', () => {
expect(multiply(3, 4)).toBe(12);
});
});
// Avec globals: true dans la config, vous pouvez omettre les imports
describe('Math functions', () => {
it('should sum two numbers', () => {
expect(sum(1, 2)).toBe(3);
});
});Mocking
// Mocking dans Vitest
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { fetchUser } from './api';
import { UserService } from './UserService';
// Mock d'un module entier
vi.mock('./api', () => ({
fetchUser: vi.fn()
}));
describe('UserService', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should fetch user by id', async () => {
// Configurer le mock
vi.mocked(fetchUser).mockResolvedValue({
id: 1,
name: 'John'
});
const service = new UserService();
const user = await service.getUser(1);
expect(fetchUser).toHaveBeenCalledWith(1);
expect(user.name).toBe('John');
});
});
// Mock de fonction spécifique
const mockFn = vi.fn();
mockFn.mockReturnValue(42);
mockFn.mockResolvedValue(asyncValue);
mockFn.mockImplementation((x) => x * 2);
// Spy sur une méthode existante
const spy = vi.spyOn(object, 'method');
spy.mockReturnValue('mocked');
// Mock des timers
vi.useFakeTimers();
vi.advanceTimersByTime(1000);
vi.runAllTimers();
vi.useRealTimers();
Tester des Composants
// Tester des composants React avec Vitest + Testing Library
// Button.test.tsx
import { describe, it, expect } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button', () => {
it('should render with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
it('should call onClick when clicked', async () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click</Button>);
await fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledOnce();
});
it('should be disabled when loading', () => {
render(<Button loading>Submit</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
});
// Setup pour Testing Library (tests/setup.ts)
import '@testing-library/jest-dom/vitest';Tester Vue
// Tester des composants Vue avec Vitest
// Counter.test.ts
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import Counter from './Counter.vue';
describe('Counter', () => {
it('should render initial count', () => {
const wrapper = mount(Counter, {
props: { initialCount: 5 }
});
expect(wrapper.text()).toContain('5');
});
it('should increment when button clicked', async () => {
const wrapper = mount(Counter);
await wrapper.find('button').trigger('click');
expect(wrapper.text()).toContain('1');
});
});Features Avancées
Browser Mode
// Tester dans un vrai browser avec Playwright
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
browser: {
enabled: true,
name: 'chromium', // ou 'firefox', 'webkit'
provider: 'playwright',
},
},
});
// Les tests s'exécutent dans un vrai browser !
// Utile pour :
// - Canvas, WebGL
// - APIs Web spécifiques
// - Visual regression
// visual.test.ts
import { describe, it, expect } from 'vitest';
import { page } from '@vitest/browser/context';
describe('Visual tests', () => {
it('should render chart correctly', async () => {
await page.goto('/chart');
// Screenshot pour visual regression
await expect(page.locator('.chart')).toMatchScreenshot();
});
});
Inline Snapshots
// Snapshots inline - plus pratique que les fichiers
import { describe, it, expect } from 'vitest';
import { formatUser } from './formatUser';
describe('formatUser', () => {
it('should format user object', () => {
const user = { name: 'John', age: 30 };
// Snapshot inline - se met à jour directement dans le fichier !
expect(formatUser(user)).toMatchInlineSnapshot(`
{
"displayName": "John (30 years old)",
"initials": "J"
}
`);
});
});
// Quand vous exécutez vitest -u, le snapshot est mis à jour
// directement dans le fichier de testType Testing
// Tester les types TypeScript
// types.test-d.ts (notez le -d dans le nom)
import { describe, it, expectTypeOf } from 'vitest';
import { createStore } from './store';
describe('Store types', () => {
it('should infer state type correctly', () => {
const store = createStore({ count: 0 });
// Teste si le type est correct
expectTypeOf(store.state.count).toBeNumber();
expectTypeOf(store.state.count).not.toBeString();
});
it('should accept correct action types', () => {
type Action = { type: 'increment' } | { type: 'decrement' };
expectTypeOf<Action>().toMatchTypeOf<{ type: string }>();
});
});
// vitest.config.ts
export default defineConfig({
test: {
typecheck: {
enabled: true,
},
},
});UI Mode
// Interface visuelle pour les tests
// Exécutez : npx vitest --ui
// Features de l'UI :
// - Visualiser tous les tests
// - Filtrer par status (passed, failed, skipped)
// - Voir le code du test
// - Voir l'output de console
// - Re-exécuter des tests spécifiques
// - Visualisation du module graph
// Excellent pour :
// - Debugger des tests qui échouent
// - Comprendre la couverture
// - Onboarding des nouveaux devs
Migrer de Jest vers Vitest
Guide de Migration
// Étapes pour migrer
const migrationGuide = {
// 1. Installer Vitest
step1: `
npm remove jest @types/jest ts-jest babel-jest
npm install -D vitest
`,
// 2. Créer la config
step2: `
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true, // Pour la compatibilité avec Jest
environment: 'jsdom',
},
});
`,
// 3. Mettre à jour les scripts
step3: `
// package.json
{
"scripts": {
"test": "vitest",
"test:run": "vitest run"
}
}
`,
// 4. Rechercher et remplacer
step4: {
from: "jest.fn()",
to: "vi.fn()",
from2: "jest.mock(",
to2: "vi.mock(",
from3: "jest.spyOn(",
to3: "vi.spyOn(",
from4: "@jest/globals",
to4: "vitest"
},
// 5. Mettre à jour les imports (si vous n'utilisez pas globals)
step5: `
// Avant
import { jest } from '@jest/globals';
// Après
import { vi } from 'vitest';
`
};Différences d'API
// Différences entre Jest et Vitest
const apiDifferences = {
// Mocking
mocking: {
jest: 'jest.fn(), jest.mock(), jest.spyOn()',
vitest: 'vi.fn(), vi.mock(), vi.spyOn()',
note: 'Seulement le préfixe différent'
},
// Timers
timers: {
jest: 'jest.useFakeTimers()',
vitest: 'vi.useFakeTimers()',
extra: 'Vitest a vi.advanceTimersByTimeAsync()'
},
// Module mocking
moduleMock: {
jest: `
jest.mock('./module', () => ({
foo: jest.fn()
}));
`,
vitest: `
vi.mock('./module', () => ({
foo: vi.fn()
}));
`,
vitestAlternative: `
// Vitest supporte aussi :
vi.mock('./module', async (importOriginal) => {
const actual = await importOriginal();
return {
...actual,
foo: vi.fn()
};
});
`
},
// Snapshot
snapshot: {
jest: 'expect(x).toMatchSnapshot()',
vitest: 'expect(x).toMatchSnapshot()',
extra: 'Vitest a toMatchInlineSnapshot()'
},
// Globals
globals: {
jest: 'Globals par défaut',
vitest: 'Nécessite globals: true ou imports explicites',
recommendation: 'Utilisez les imports explicites pour la type safety'
}
};Codemod Automatique
// Utilisez codemod pour une migration automatisée
// Installer
// npx @vitest/codemod jest
// Le codemod fait :
// - Renomme jest.* en vi.*
// - Met à jour les imports
// - Ajuste la configuration
// Ensuite, révisez manuellement :
// - Mocks complexes
// - Fichiers de setup
// - Custom matchers
Bonnes Pratiques avec Vitest
Organisation des Tests
// Structure recommandée
const testStructure = {
// Option 1 : Co-localisé (recommandé)
colocated: `
src/
components/
Button/
Button.tsx
Button.test.tsx ← À côté du code
Button.stories.tsx
utils/
format.ts
format.test.ts ← À côté du code
`,
// Option 2 : Dossier séparé
separate: `
src/
components/
Button.tsx
tests/
components/
Button.test.tsx ← Dossier séparé
`,
recommendation: `
Co-localisé est mieux :
- Facile de trouver les tests
- Refactoriser ensemble
- Encourage l'écriture de tests
`
};Patterns Utiles
// Patterns qui fonctionnent bien
// 1. Factory functions pour les fixtures
function createUser(overrides = {}) {
return {
id: 1,
name: 'John',
email: 'john@example.com',
...overrides
};
}
it('should update user name', () => {
const user = createUser({ name: 'Jane' });
// ...
});
// 2. Custom matchers
expect.extend({
toBeValidEmail(received) {
const pass = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(received);
return {
pass,
message: () => `expected ${received} to be valid email`
};
}
});
// 3. Testing utilities
function renderWithProviders(ui, options = {}) {
return render(ui, {
wrapper: ({ children }) => (
<QueryClientProvider client={queryClient}>
<ThemeProvider>
{children}
</ThemeProvider>
</QueryClientProvider>
),
...options
});
}
// 4. Blocs describe organisés
describe('UserService', () => {
describe('getUser', () => {
it('should return user when found', () => {});
it('should throw when not found', () => {});
});
describe('createUser', () => {
it('should create user with valid data', () => {});
it('should reject invalid email', () => {});
});
});
Conclusion
Vitest est devenu le standard des tests en JavaScript pour de bonnes raisons : il est plus rapide, plus moderne, et a une meilleure DX que Jest. La migration est simple et les bénéfices sont immédiats.
Quand utiliser Vitest :
- Nouveaux projets (toujours)
- Projets avec Vite (migration facile)
- Projets qui ont besoin d'ESM
- Quand la performance des tests compte
Quand garder Jest :
- Projets legacy qui fonctionnent bien
- Le coût de migration ne se justifie pas
- Dépendances qui nécessitent Jest
Actions recommandées :
- Utilisez Vitest dans les nouveaux projets
- Planifiez la migration des projets actifs
- Explorez le browser mode pour les tests visuels
- Utilisez le UI mode pour le debug
Pour en savoir plus sur l'écosystème JavaScript moderne, lisez : VoidZero 2026.

