Blog / Article #127
Symfony et Vitest : le duo improbable qui teste ton JS

star

09 Septembre 2026
Symfony et Vitest

Symfony et JavaScript, c’est un peu comme un couple de colocataires qui s’entendent bien… mais qui ne partagent pas le même frigo. D’un côté, ton backend PHP, structuré, mature, avec ses bonnes pratiques et ses bin/console qui te sauvent la mise. De l’autre, ton frontend JS, ce farfadet capricieux qui change d’avis sur son framework préféré tous les six mois. Et entre les deux, il faut bien faire communiquer tout ce petit monde sans que ça tourne au drame.

Si tu es du genre à vouloir tester ton JavaScript sans te coltiner la lourdeur de Jest (oui, on sait que tu rames avec ses node_modules qui pèsent plus lourd que ton âme), Vitest est peut-être ton nouveau meilleur pote. Léger, rapide, compatible avec Vite (ce qui tombe bien, Symfony adore Vite depuis la version 6.3), et surtout : il ne te force pas à réinventer la roue.
Alors, on y va ?

Pourquoi Vitest et pas Jest ? Juste pour éviter ces node_modules obèses

Jest, c’est un peu le Microsoft Word des tests JavaScript : tout le monde l’utilise, tout le monde râle, mais personne ne sait vraiment pourquoi. Il est lent, il est lourd, et il a cette fâcheuse tendance à te faire attendre trois plombes pour lancer un test qui échoue lamentablement à cause d’une virgule oubliée. Vitest, lui, c’est le SublimeText de la situation : simple, efficace, et il ne te prend pas la tête avec des fonctionnalités que tu n’utiliseras jamais.

Voici pourquoi Vitest mérite ton attention :

  • Vitesse : Vitest tourne sur Vite, ce qui signifie qu’il profite du Hot Module Replacement (HMR) et d’un démarrage quasi instantané. Pas besoin de patienter comme si tu attendais un RER un lundi matin.
  • Compatibilité : Vitest est conçu pour être un drop-in replacement de Jest. Si tu as déjà des tests Jest, tu peux les migrer sans tout casser (en théorie).
  • Écosystème : Il supporte les mêmes assertions que Jest (expect, toBe, etc.), les mocks, les snapshots, et même les tests en parallèle. Bref, tout ce que tu aimes chez Jest, mais en mieux.
  • Intégration avec Vite : Si tu utilises déjà Vite avec Symfony (via symfony/webpack-encore ou directement), Vitest s’intègre naturellement. Pas besoin de configurer un énième bundler.

Alors oui, Jest a une communauté plus large, mais est-ce que ça vaut vraiment le coup de s’encombrer d’un outil qui met cinq minutes à démarrer ? mmm, ..Bah non !

Installer Vitest dans un projet Symfony : la marche à suivre (sans les pièges)

Passons aux choses sérieuses. Voici comment intégrer Vitest dans ton projet Symfony, sans te perdre dans les méandres de la documentation officielle (qui, avouons-le, est parfois aussi claire qu’un contrat d’assurance).

Première étape : installer les dépendances. On part du principe que tu as déjà Vite configuré dans ton projet (si ce n’est pas le cas, composer require symfony/ux-vite est ton ami). Ensuite, installe Vitest et ses petits copains :

npm install -D vitest @vitejs/plugin-react jsdom

Pourquoi jsdom ? Parce que Vitest, par défaut, tourne dans un environnement Node.js, et si tu veux tester du code qui manipule le DOM (comme un composant React ou un script vanilla), il te faut un environnement qui simule un navigateur. jsdom fait ça très bien, sans te forcer à lancer un vrai navigateur (ce qui serait lent, et on a déjà établi que la lenteur, c’est mal).

Ensuite, configure Vite pour qu’il sache qu’il doit utiliser Vitest. Modifie ton fichier vite.config.js (ou vite.config.ts si tu es du genre à kiffer TypeScript) :

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './tests/setup.js',
  },
});

Quelques explications sur cette config :

  • globals: true : ça te permet d’utiliser describe, it, expect sans les importer à chaque fois. Pratique, mais si tu es du genre puriste, tu peux t’en passer.
  • environment: 'jsdom' : comme expliqué plus haut, ça simule un navigateur pour tes tests.
  • setupFiles : un fichier optionnel où tu peux configurer des trucs globaux pour tes tests (comme des mocks ou des polyfills).

Maintenant, ajoute un script dans ton package.json pour lancer tes tests :

"scripts": {
  "test": "vitest",
  "test:watch": "vitest watch"
}

Et voilà ! Tu peux maintenant lancer tes tests avec npm test ou npm run test:watch si tu veux que Vitest surveille tes fichiers et relance les tests automatiquement (ce qui est, avouons-le, la moindre des choses en 2024).

Écrire des tests avec Vitest : parce que le lint ne teste pas ton code

Un petit rappel qui ne fait jamais de mal : ESLint et Vitest ne font pas le même boulot. ESLint regarde ton code et te dit « ça, c’est moche », « ça, c’est probablement une erreur » ou « tu as déclaré une variable pour absolument rien ». C’est très bien, mais ça ne garantit pas que ton code fonctionne.

Vitest, lui, exécute réellement ton JavaScript et vérifie son comportement. Prenons volontairement un exemple d’une complexité intellectuelle assez violente :

export function addition(a, b) {
    return a + b;
}

On peut lui coller un test dans tests/addition.test.js :

import { describe, expect, it } from 'vitest';
import { addition } from '../assets/js/addition.js';

describe('addition', () => {
    it('additionne deux nombres', () => {
        expect(addition(2, 3)).toBe(5);
    });
});

Puis :

npm test

Vitest trouve les fichiers de test, exécute le code et te dit si tout va bien. Jusque-là, rien de révolutionnaire. L’intérêt arrive surtout quand on commence à tester du vrai code applicatif : validation d’un formulaire, transformation de données, appels HTTP, fonctions utilitaires ou comportement d’un composant.

Tester du code qui touche au DOM

Dans un projet Symfony, le JavaScript ne sert pas toujours à calculer 2 + 3. Il va souvent chercher un bouton, modifier une classe CSS, afficher un message ou réagir à un événement. C’est justement là que jsdom devient utile.

Imaginons cette fonction :

export function afficherMessage(message) {
    const element = document.querySelector('#message');

    if (element) {
        element.textContent = message;
    }
}

On peut la tester sans ouvrir Chrome, Firefox ou sacrifier un stagiaire :

import { beforeEach, describe, expect, it } from 'vitest';
import { afficherMessage } from '../assets/js/message.js';

describe('afficherMessage', () => {
    beforeEach(() => {
        document.body.innerHTML = '<div id="message"></div>';
    });

    it('affiche le message dans le DOM', () => {
        afficherMessage('Bonjour Gérard');

        expect(document.querySelector('#message').textContent)
            .toBe('Bonjour Gérard');
    });
});

Ce n’est toujours pas un vrai navigateur, attention. jsdom reproduit suffisamment le DOM pour tester beaucoup de comportements JavaScript classiques, mais il ne remplace pas un outil de test end-to-end comme Playwright lorsqu’on veut vraiment piloter une page dans Chromium ou Firefox.

Et la couverture de code ?

Tester, c’est bien. Savoir quelle partie du JavaScript n’est jamais passée dans les tests, c’est encore mieux. Vitest sait produire un rapport de couverture avec V8.

On commence par installer le provider :

npm install -D @vitest/coverage-v8

Puis on ajoute par exemple :

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

Petite subtilité importante : dans une CI, utilise plutôt vitest run. La commande vitest est surtout pratique en local puisqu’elle peut fonctionner en mode interactif/watch. Ton runner GitLab, lui, n’a pas besoin de contempler tes fichiers en attendant que quelqu’un les modifie.

Ensuite :

npm run test:coverage

Tu obtiens alors un rapport avec la couverture des lignes, fonctions et branches de ton code JavaScript. Et surtout, tu peux produire un fichier lcov.info, format compris notamment par SonarQube.

Dans la configuration Vitest :

export default defineConfig({
  test: {
    environment: 'jsdom',
    coverage: {
      provider: 'v8',
      reporter: ['text', 'lcov'],
    },
  },
});

Le rapport se retrouvera typiquement dans :

coverage/lcov.info

Vitest dans GitLab CI : là, ça devient intéressant

Faire tourner les tests sur ton poste, c’est bien gentil. Mais le but est surtout d’empêcher Michel de pousser un JavaScript cassé un vendredi à 16 h 58 avant de partir en week-end.

Pas besoin ici de chercher une mystérieuse image Docker « spéciale Vitest ». Vitest est un outil Node.js : une image officielle Node suffit parfaitement.

test:js:
  stage: test
  image: node:24-alpine
  before_script:
    - npm ci
  script:
    - npm run test:coverage
  artifacts:
    when: always
    paths:
      - coverage/
    expire_in: 1 week

C’est finalement le pendant JavaScript de ce qu’on fait avec PHPUnit côté PHP : installation reproductible des dépendances, exécution des tests, génération de la couverture et conservation du rapport pour la suite de la pipeline.

Et si SonarQube analyse déjà le projet, on peut lui indiquer où trouver la couverture JavaScript dans sonar-project.properties :

sonar.javascript.lcov.reportPaths=coverage/lcov.info

On commence alors à avoir quelque chose de propre : PHPUnit surveille le PHP, Vitest surveille le JavaScript, et SonarQube récupère les rapports pour avoir une vision plus globale du projet.

Et le SCSS dans tout ça ?

C’est là qu’il faut distinguer test et analyse statique. Tester du SCSS avec un framework de tests unitaires n’a pas beaucoup de sens dans la majorité des projets. Une feuille de style n’a pas vraiment de « comportement » à tester comme une fonction PHP ou JavaScript.

En revanche, on peut parfaitement vérifier sa qualité avec Stylelint :

npm install -D stylelint stylelint-config-standard-scss

Puis ajouter un script :

"scripts": {
  "lint:scss": "stylelint \"assets/**/*.scss\""
}

Stylelint va repérer les erreurs, les propriétés douteuses, certaines incohérences et faire respecter les conventions choisies. On est donc davantage dans l’équivalent d’un PHP-CS-Fixer ou d’une analyse statique que dans celui de PHPUnit.

Pour tester le rendu réel d’une interface, en revanche, on change encore de catégorie. Il faut plutôt regarder du côté de Playwright et des tests end-to-end ou de la comparaison visuelle. Mais ça, c’est une autre couche et probablement un autre article.

Au final : PHP et JavaScript peuvent partager le même frigo^^

Ajouter Vitest à un projet Symfony ne transforme pas soudainement ton frontend en centrale nucléaire certifiée par la NASA. Mais ça permet enfin de traiter le JavaScript comme du vrai code applicatif, et pas comme une poignée de fichiers qu’on balance dans assets/ en espérant que personne n’y touche.

Avec PHPUnit pour le PHP, Vitest pour le JavaScript, ESLint pour surveiller les conneries syntaxiques, Stylelint pour le SCSS et SonarQube au-dessus pour centraliser une partie des métriques, on obtient une chaîne de qualité plutôt cohérente.

Et surtout, Vitest ne demande pas une infrastructure particulière : Node.js, quelques dépendances dans le projet et une image node dans la CI suffisent. Pas besoin d’une image Docker magique de 4 Go baptisée ultimate-frontend-quality-enterprise-edition.

Le JavaScript continuera probablement à changer de framework préféré tous les six mois. Mais au moins, maintenant, quand il cassera quelque chose, la CI pourra te le dire avant les utilisateurs.

Publié le 09/09/2026 · 9 min de lecture Partager X LinkedIn

← Précédent Proxmox et le trim : quand vos vms jouent à cache-cache avec l’espace disque