Retour au blog

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 test

Type 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 :

  1. Utilisez Vitest dans les nouveaux projets
  2. Planifiez la migration des projets actifs
  3. Explorez le browser mode pour les tests visuels
  4. Utilisez le UI mode pour le debug

Pour en savoir plus sur l'écosystème JavaScript moderne, lisez : VoidZero 2026.

Allons-y ! 🦅

Commentaires (0)

Cet article n'a pas encore de commentaires. Soyez le premier!

Ajouter des commentaires