📧 Reste informé(e) !

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

S'inscrire gratuitement

~6 min de lecture

Vitest config Angular : setup, jsdom et couverture de code

Tu as Vitest qui tourne sur ton projet Angular, mais tu te demandes ce que fait vraiment chaque ligne de vitest.config.ts ? Ou comment activer une couverture de code propre, avec des seuils qui font échouer la CI quand on descend trop bas ?

Cet article décortique la configuration Vitest pour Angular, ligne par ligne : le fichier de config, le test-setup.ts, l'environnement jsdom, et toute la partie couverture (provider, reporters, seuils, exclusions) plus l'intégration en CI.

Avant de plonger, il faut savoir qu'il existe aujourd'hui deux mondes pour faire tourner Vitest sur Angular :

  1. Le builder officiel @angular/build:unit-test (expérimental en Angular 20.2, runner par défaut en 21). La CLI gère la config Vitest et l'initialisation de l'environnement de test pour toi via angular.json. Tu n'écris quasiment pas de vitest.config.ts ni de test-setup.ts.
  2. Le plugin AnalogJS (@analogjs/vite-plugin-angular). Tu écris un vitest.config.ts explicite plus un fichier de setup. Contrôle total sur chaque option.

La suite de cet article (l'anatomie de vitest.config.ts et le test-setup.ts) détaille la route AnalogJS. La partie couverture, elle, reste pertinente dans les deux cas : même avec le builder officiel, tu peux personnaliser la couverture.

Tu pars de zéro et tu viens de Karma ? Lis d'abord Karma to Vitest : migrer ses tests Angular. Pour le détail du mode zoneless, vois le guide Vitest zoneless.


1. Anatomie de vitest.config.ts

Voici une config Angular complète et commentée :

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

export default defineConfig(() => ({
  // Le plugin qui sait compiler les composants Angular
  plugins: [angular()],
  test: {
    // Expose describe/it/expect/vi sans import explicite
    globals: true,
    // Simule un DOM pour tester les composants
    environment: 'jsdom',
    // Fichier exécuté une fois avant toute la suite
    setupFiles: ['src/test-setup.ts'],
    // Quels fichiers sont des tests
    include: ['src/**/*.spec.ts'],
    // Ce qu'on n'exécute pas
    exclude: ['node_modules', 'dist'],
  },
}));

Détaillons les options qui comptent :

  • plugins: [angular()] : indispensable. Sans lui, Vitest ne sait pas compiler les templates et décorateurs Angular.
  • globals: true : permet d'écrire describe(...) sans import { describe } from 'vitest'. Pratique, mais pense à ajouter vitest/globals aux types de ton tsconfig.spec.json pour que TypeScript suive.
  • environment: 'jsdom' : simule un navigateur léger. Alternative : happy-dom, plus rapide mais parfois moins complet. Pour Angular, jsdom est le choix sûr.
  • setupFiles : pointe vers le fichier qui initialise le TestBed (voir section suivante).
  • include / exclude : restreignent la découverte des tests.

Rappel : si tu es sur le builder officiel @angular/build:unit-test, cette config est gérée par la CLI via angular.json et tu n'écris pas ce fichier. Le comprendre reste utile pour personnaliser, notamment la couverture (section 3).


2. Le fichier src/test-setup.ts

Ce fichier s'exécute une seule fois avant toute la suite de tests. Son rôle : initialiser l'environnement de test Angular. Il 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…) : à placer ici, une fois pour toutes.

Ce qu'on y fait :

  • On importe @angular/compiler pour compiler les composants à la volée.
  • On initialise le TestBed global avec getTestBed().initTestEnvironment(...).
  • On garde l'environnement minimal : c'est aussi l'endroit où placer des mocks globaux (par exemple IntersectionObserver ou matchMedia, absents de jsdom) si plusieurs suites en ont besoin.

Le zoneless va côté configureTestingModule

Angular 21 est zoneless par défaut. Mais ne force pas le zoneless via un @NgModule wrapper dans initTestEnvironment : la façon idiomatique est de fournir provideZonelessChangeDetection() par test, dans le TestBed.configureTestingModule({...}) de chaque suite :

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

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

(C'est bien provideZonelessChangeDetection(), l'API stable, et non l'ancien provideExperimentalZonelessChangeDetection.)

Aligner le DI des tests sur l'app via appConfig

Plutôt que de reconstruire les providers à la main suite après suite, tu peux partir de la même configuration que ton app de prod en réutilisant son appConfig :

import { appConfig } from '../app/app.config';
import { TestBed } from '@angular/core/testing';

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

On ne rajoute pas provideZonelessChangeDetection() ici : si ton app est zoneless, il est déjà dans appConfig.providers — tu en hérites gratuitement. C'est précisément l'intérêt de partir de la vraie config.

Cette approche colle à un test sociable (style « Detroit » / classiciste) : tu gardes les vrais collaborateurs in-process (le router et ses vraies routes, les pipes, les services) et tu ne remplaces que ce qui sort du process ou n'est pas déterministe — typiquement le réseau (provideHttpClientprovideHttpClientTesting()) — en retirant les providers à effets de bord hors-test (analytics, providers SSR-only qui cassent sous jsdom). Pas de « router de test » : les vraies routes font partie de la fidélité que tu cherches.

À l'inverse, pour un test unitaire isolé (style « London » / mockiste), ne pars pas de appConfig : construis un module de test minimal avec uniquement les doublures dont l'unité a besoin.


3. Configurer la couverture de code

La couverture mesure quelle part de ton code est exécutée par les tests. Vitest l'intègre nativement.

Choisir le provider : v8 ou istanbul

Deux options :

  • v8 (recommandé par défaut) : utilise l'instrumentation native du moteur V8. Très rapide, zéro transformation de code supplémentaire.
  • istanbul : plus mature historiquement, parfois plus précis sur le mapping des branches, mais plus lent car il instrumente le code.

Installe le provider correspondant :

# Provider v8 (recommandé)
pnpm add -D @vitest/coverage-v8

# ou provider istanbul
pnpm add -D @vitest/coverage-istanbul

La config de couverture complète

/// <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'],
    coverage: {
      provider: 'v8',
      // Les formats de rapport générés
      reporter: ['text', 'html', 'lcov'],
      reportsDirectory: './coverage',
      // Ne mesure que le vrai code applicatif
      include: ['src/**/*.ts'],
      exclude: [
        'src/**/*.spec.ts',
        'src/test-setup.ts',
        'src/main.ts',
        'src/**/*.config.ts',
        'src/**/*.routes.ts',
        '**/*.d.ts',
      ],
      // Fait échouer la commande sous ces seuils
      thresholds: {
        statements: 80,
        branches: 75,
        functions: 80,
        lines: 80,
      },
    },
  },
}));

Les options clés :

  • reporter : text affiche un tableau dans le terminal, html génère un rapport navigable dans ./coverage, et lcov produit le format consommé par les outils CI (Codecov, SonarQube, GitLab…).
  • include / exclude : ne mesure que le code qui compte. Exclure les specs, le bootstrap, les fichiers de config et de routes évite de fausser le pourcentage.
  • thresholds : des planchers. Si la couverture passe sous l'un d'eux, la commande de test sort en erreur — parfait pour bloquer une PR qui dégrade la qualité.

Lance la couverture avec :

pnpm vitest run --coverage

N'inclus pas tes fichiers .spec.ts dans le calcul de couverture, sinon tu mesures la couverture de tes tests. Vérifie aussi que reportsDirectory (par défaut ./coverage) est bien dans ton .gitignore.


4. Intégration en CI

Pour la CI, l'objectif est un run non interactif (pas de watch) avec couverture. Ajoute des scripts dans ton package.json :

{
  "scripts": {
    "test": "vitest",
    "test:ci": "vitest run --coverage"
  }
}

vitest sans argument démarre en watch mode (idéal en local), tandis que vitest run exécute une passe unique et sort — c'est ce qu'il faut en CI.

Exemple d'étape GitHub Actions :

- name: Install
  run: pnpm install --frozen-lockfile

- name: Test + coverage
  run: pnpm test:ci

Le job échouera automatiquement si un test casse ou si la couverture passe sous tes seuils. Le rapport lcov peut ensuite être envoyé à ton outil de suivi de couverture.

Sur un monorepo Nx, passe par nx test (ou nx affected -t test) pour ne rejouer que les projets impactés. Nx orchestre Vitest tout en gérant le cache et le graphe de dépendances.


5. Les pièges fréquents de config

  • Oublier vitest/globals dans tsconfig.spec.json : avec globals: true, TypeScript râle sur describe/expect tant que tu n'as pas ajouté "types": ["vitest/globals", "node"].
  • jsdom incomplet : IntersectionObserver, ResizeObserver, matchMedia n'existent pas. Mocke-les dans test-setup.ts une fois pour toutes.
  • Couverture qui inclut tout : sans include/exclude ciblés, tes pourcentages sont faussés par le code de bootstrap et les configs.
  • Seuils trop ambitieux d'un coup : commence par tes valeurs actuelles, puis remonte les seuils progressivement. Mettre 100 % dès le départ casse la CI et décourage l'équipe.

Conclusion

Une bonne config Vitest pour Angular tient en trois fichiers : un vitest.config.ts clair, un test-setup.ts qui initialise le TestBed en zoneless, et une section coverage avec des seuils qui protègent ta CI. Une fois ces bases posées, tu profites d'une boucle de feedback rapide en local et d'un garde-fou solide en intégration continue.

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.