📧 Reste informé(e) !

Reçois les derniers articles et conseils EasyAngularKit directement dans ta boîte mail.

S'inscrire gratuitement

~6 min de lecture

Karma to Vitest : migrer ses tests Angular sans douleur

Si tu maintiens un projet Angular un peu ancien, tes tests tournent probablement encore sous Karma + Jasmine. Le problème : Karma est officiellement déprécié depuis Angular 16 (annonce de l'équipe Angular en 2023), et Vitest est devenu le runner par défaut des nouveaux projets en Angular 21. Le moment est donc idéal pour migrer.

Dans cet article, on voit pourquoi migrer, comment mettre Vitest en place sur Angular, et surtout la table d'équivalences Jasmine → Vitest pour traduire tes tests existants sans tout réécrire de zéro.

Si tu cherches le détail du setup zoneless avec Vitest, on a un guide dédié : Vitest + Angular zoneless. Ici, on se concentre sur la migration depuis Karma.


1. Pourquoi quitter Karma ?

Plusieurs raisons, et elles s'accumulent :

  • Karma est déprécié. L'équipe Angular l'a annoncé en 2023, avec Angular 16. Plus de nouvelles fonctionnalités, maintenance minimale.
  • La vitesse. Karma lance un vrai navigateur et recharge tout à chaque run. Vitest tourne sur Vite + esbuild, avec un watch mode quasi instantané et de la parallélisation.
  • L'ESM. Vite est nativement ESM ; fini les contorsions de bundling. Vitest profite directement de l'écosystème Vite.
  • L'expérience moderne. API proche de Jest, snapshots, expect riche, mocking intégré (vi), couverture out-of-the-box.
  • C'est la direction officielle. Depuis Angular 21, ng new génère un projet avec Vitest par défaut. Karma appartient au passé.

2. Prérequis avant de migrer

Avant de te lancer, vérifie quelques points :

  • Une version d'Angular récente (20+ idéalement). Plus ta version est haute, plus le support Vitest est intégré.
  • Un projet standalone (pas de NgModule legacy). Ce n'est pas bloquant, mais ça simplifie le setup.
  • Une CI verte avant de commencer, pour comparer avant/après.
  • Du temps pour la table d'équivalences : la migration de l'outillage est rapide, c'est la traduction des spies/mocks qui prend le plus de temps sur un gros projet.

3. Deux chemins pour installer Vitest

Il existe aujourd'hui deux approches. Choisis selon ta version d'Angular.

Option A — Le builder officiel (Angular 20.2+)

Depuis Angular 20.2, un builder expérimental est livré avec la CLI : @angular/build:unit-test. Il s'appuie sur Vitest sous le capot, sans dépendance tierce.

Active-le dans ton angular.json :

{
  "test": {
    "builder": "@angular/build:unit-test",
    "options": {
      "tsConfig": "tsconfig.spec.json",
      "runner": "vitest",
      "buildTarget": "::development"
    }
  }
}

Puis installe simplement le runner et l'environnement DOM :

pnpm add -D vitest jsdom

C'est l'option à privilégier sur les projets récents : moins de dépendances, alignée sur la roadmap Angular.

Option B — Le plugin AnalogJS (toutes versions 20+)

Si tu es sur une version plus ancienne ou que tu veux un contrôle total sur vitest.config.ts, passe par le plugin maintenu par l'équipe AnalogJS, qui sait compiler les composants Angular pour Vitest :

pnpm add -D vitest jsdom @analogjs/vitest-angular @analogjs/vite-plugin-angular

Les deux chemins mènent au même résultat. La suite de l'article reste valable quel que soit ton choix.


4. Désinstaller l'ancien monde

Une fois Vitest en place, on dit adieu à Karma et Jasmine :

pnpm remove \
  @types/jasmine jasmine-core karma \
  karma-chrome-launcher karma-coverage \
  karma-jasmine karma-jasmine-html-reporter

Supprime aussi le fichier karma.conf.js s'il existe encore à la racine.


5. La config Vitest minimale

Avec l'option AnalogJS, crée un vitest.config.ts :

/// <reference types="vitest" />
import { defineConfig } from 'vite';
import angular from '@analogjs/vite-plugin-angular';

export default defineConfig(() => ({
  plugins: [angular()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['src/test-setup.ts'],
    include: ['src/**/*.spec.ts'],
  },
}));

Et le fichier de setup src/test-setup.ts, qui initialise l'environnement de test une seule fois. Ce fichier est propre à la route AnalogJS : avec le builder officiel @angular/build:unit-test, tu n'écris pas ce fichier, la CLI initialise l'environnement pour toi.

import '@angular/compiler';
import { getTestBed } from '@angular/core/testing';
import {
  BrowserTestingModule,
  platformBrowserTesting,
} from '@angular/platform-browser/testing';

getTestBed().initTestEnvironment(
  BrowserTestingModule,
  platformBrowserTesting(),
);

// Mocks globaux jsdom (matchMedia, IntersectionObserver…) : ici, une fois pour toutes.

Pour le mode zoneless (la norme depuis Angular 21), ne passe pas par un @NgModule wrapper dans initTestEnvironment. La façon idiomatique est de fournir provideZonelessChangeDetection() par test, dans le TestBed.configureTestingModule({...}) :

import { provideZonelessChangeDetection } from '@angular/core';
import { TestBed } from '@angular/core/testing';

TestBed.configureTestingModule({
  providers: [provideZonelessChangeDetection()],
});

Si tes tests détectent encore le changement via Zone.js, ils risquent de ne pas voir les mises à jour en zoneless. On détaille ce point juste après.


6. Le cœur de la migration : Jasmine vers Vitest

Bonne nouvelle : l'API est très proche. describe, it, expect, beforeEach existent à l'identique. L'essentiel du travail concerne les spies et les mocks.

Jasmine / Karma Vitest Note
describe / it / expect describe / it / expect Identique
beforeEach / afterEach beforeEach / afterEach Identique
jasmine.createSpy() vi.fn() Crée une fonction espionne
spyOn(obj, 'method') vi.spyOn(obj, 'method') Espionne une méthode existante
.and.returnValue(x) .mockReturnValue(x) Valeur de retour fixe
.and.callFake(fn) .mockImplementation(fn) Implémentation custom
.and.callThrough() vi.spyOn(...) (comportement par défaut) Vitest passe par défaut
jasmine.createSpyObj('s', ['a']) { a: vi.fn() } Objet de mocks à la main
expect(spy).toHaveBeenCalled() expect(spy).toHaveBeenCalled() Identique
expect(x).toEqual(y) expect(x).toEqual(y) Identique
expect(x).toBe(y) expect(x).toBe(y) Identique
jasmine.clock() vi.useFakeTimers() Faux timers
jasmine.any(Type) expect.any(Type) Matcher de type
(reset auto) vi.clearAllMocks() À appeler dans beforeEach si besoin

Avant (Jasmine)

describe('UserService', () => {
  it('charge un utilisateur', () => {
    const http = jasmine.createSpyObj('HttpClient', ['get']);
    http.get.and.returnValue(of({ id: 1, name: 'Ada' }));

    const service = new UserService(http);
    service.load(1);

    expect(http.get).toHaveBeenCalledWith('/api/users/1');
  });
});

Après (Vitest)

import { describe, it, expect, vi } from 'vitest';
import { of } from 'rxjs';

describe('UserService', () => {
  it('charge un utilisateur', () => {
    const http = { get: vi.fn().mockReturnValue(of({ id: 1, name: 'Ada' })) };

    const service = new UserService(http as any);
    service.load(1);

    expect(http.get).toHaveBeenCalledWith('/api/users/1');
  });
});

Avec globals: true dans la config, tu peux même omettre l'import de describe/it/expect/vi. À toi de voir : les imports explicites restent plus clairs pour l'IDE.


7. Les pièges à connaître

jsdom n'est pas un vrai navigateur

Vitest tourne sous jsdom, pas Chrome. La plupart des tests passent sans souci, mais certaines API navigateur (layout, IntersectionObserver, matchMedia) doivent être mockées. Pour des tests qui exigent un vrai moteur de rendu, regarde le mode navigateur de Vitest (Playwright), mais pour 95 % des tests unitaires, jsdom suffit.

Le zoneless change la détection de changement

En zoneless, plus de Zone.js pour déclencher automatiquement la détection de changement. Dans tes tests de composants, pense à appeler fixture.detectChanges() explicitement, et privilégie l'API moderne basée sur les signals. C'est exactement le sujet de notre guide Vitest zoneless.

Le TestBed reste le même

Bonne nouvelle : TestBed.configureTestingModule({...}), TestBed.inject(), TestBed.createComponent() fonctionnent à l'identique. Tu ne touches pas à la logique de tes tests de composants, juste aux spies. Pour aligner le DI de tes tests sur l'app réelle, tu peux réutiliser ton appConfig.providers comme point de départ iso, à curer ensuite : on détaille ce pattern dans le guide config Vitest.

done callback → async/await

Jasmine permettait un callback done. Avec Vitest, préfère les fonctions async et await, plus lisibles et sans risque de timeout silencieux.


8. Karma vs Vitest en un coup d'œil

Critère Karma + Jasmine Vitest
Statut Déprécié Runner par défaut Angular 21+
Moteur Vrai navigateur jsdom (ou navigateur via Playwright)
Vitesse Lente, recharge complète Rapide, esbuild + parallélisation
Watch mode Lourd Quasi instantané (HMR)
Modules CommonJS / SystemJS ESM natif
Mocking jasmine.createSpy vi.fn / vi.mock
Couverture karma-coverage v8 / istanbul intégrés
Config karma.conf.js vitest.config.ts ou builder CLI

Conclusion

Migrer de Karma à Vitest se résume à trois mouvements : installer Vitest (builder officiel ou plugin AnalogJS), désinstaller Karma/Jasmine, puis traduire tes spies avec la table d'équivalences. Le TestBed ne bouge pas, l'API de test est quasi identique, et tu y gagnes une vitesse et une expérience de développement sans commune mesure.

Pour aller plus loin :

📧 Reste informé(e) !

Reçois les derniers articles et conseils EasyAngularKit directement dans ta boîte mail.

S'inscrire gratuitement

AngularKit

Suite d'outils pour développeurs Angular francophones. Apprends, modernise tes réflexes, audite ta codebase.

Produits

Contact

Légal

© 2026 AngularKit. Tous droits réservés.