Aller au contenu principal

Tester avec Vitest

Écrire des programmes qui vérifient votre programme, et dormir tranquille avant chaque modification.

Objectifs de la séance​

  • comprendre pourquoi et quoi tester ;
  • installer et configurer Vitest avec Testing Library ;
  • écrire des tests avec describe, it et expect ;
  • tester une fonction pure, un reducer et une validation ;
  • simuler fetch pour tester le chargement sans réseau ;
  • tester un composant comme un utilisateur (saisie, clic, message d'erreur).

Petite vérification​

Avant de commencer, vérifiez si vous savez...

  1. taper /resultat?score=9&total=3 et lire le message affiché
  2. dire pourquoi le pseudo n'est plus dans l'adresse de la partie
  3. dire ce qui se passe, dans le hook useJoueur, quand aucun JoueurProvider n'existe au-dessus du composant
📌 Réponse attendue

Notions théoriques​

Pourquoi tester ?​

À chaque modification, vous risquez de casser quelque chose qui marchait. Vérifier à la main tous les parcours (trois niveaux, bons et mauvais pseudos, adresses truquées...) prend du temps, et on finit par ne plus le faire. Un test automatique est un petit programme qui exécute votre code et vérifie le résultat. Une fois écrit, il se relance en une seconde, autant de fois que nécessaire.

Quoi tester ?​

Ce qu'on testeDifficultéExemple du quiz
Une fonction puretrès facilecalculerPourcentage, lireNiveau
Un reducertrès facilepartieReducer : le double clic est ignoré
Un chargement (avec fetch simulé)moyenchargerQuestions : 404, données invalides
Un composant, comme un utilisateurmoyenFormulaireDemarrage : pseudo trop court

C'est la raison pour laquelle la logique a été rangée dans src/logic/ : une fonction pure se teste en trois lignes, sans navigateur.

Installer l'outillage​

npm install -D vitest jsdom @testing-library/react @testing-library/user-event @testing-library/jest-dom
PaquetRôle
vitestlance les tests (même moteur que Vite)
jsdomsimule un navigateur dans Node.js
@testing-library/reactaffiche un composant et le cherche comme un utilisateur
@testing-library/user-eventsimule la saisie et les clics
@testing-library/jest-domajoute des vérifications utiles (toBeInTheDocument...)

Configurer​

vite.config.ts : on importe defineConfig depuis vitest/config et on ajoute la section test.

vite.config.ts
import react from "@vitejs/plugin-react";
import { defineConfig } from "vitest/config";

export default defineConfig({
plugins: [react()],
test: {
environment: "jsdom",
setupFiles: "./src/setupTests.ts",
},
});

Le fichier d'initialisation prépare les vérifications et nettoie l'écran entre deux tests :

src/setupTests.ts
import "@testing-library/jest-dom/vitest";
import { cleanup } from "@testing-library/react";
import { afterEach } from "vitest";

afterEach(() => {
cleanup();
});

Sans cleanup, les composants affichés par un test resteraient dans la page et perturberaient le suivant (message « plusieurs éléments trouvés »).

Enfin, ajoutez la commande dans la section scripts de package.json : "test": "vitest run".

Anatomie d'un test​

src/logic/score.test.ts
import { describe, expect, it } from "vitest";
import { calculerPourcentage } from "./score";

describe("calculerPourcentage", () => {
it("arrondit au pourcentage le plus proche", () => {
expect(calculerPourcentage(7, 10)).toBe(70);
expect(calculerPourcentage(2, 3)).toBe(67);
});

it("renvoie 0 quand il n'y a aucune question", () => {
expect(calculerPourcentage(0, 0)).toBe(0);
});
});
  • describe regroupe les tests d'une même chose ;
  • it("...", () => { ... }) est un test : le texte décrit le comportement attendu, en français ;
  • expect(valeur).toBe(attendu) vérifie ; si la valeur diffère, le test échoue et indique l'écart.

Le nom d'un fichier de test se termine par .test.ts (ou .test.tsx) et se place à côté du fichier testé.

VérificationRôle
toBe(x)égalité stricte (nombres, textes, même objet)
toEqual(x)égalité du contenu de deux objets ou tableaux
toBeNull(), toHaveLength(n)valeur nulle, longueur d'un tableau
rejects.toThrow("...")une promesse doit être rompue avec une erreur

La commande npm test lance tous les tests et affiche, par exemple, Tests 17 passed (17).

Tester un reducer​

Un reducer est une fonction pure : on lui donne un état et une action, on vérifie le nouvel état. Le test du double clic est particulièrement précieux :

it("ignore un second choix (double clic)", () => {
const premier = partieReducer(etatInitial, { type: "CHOISIR", reponseId: 13, estCorrecte: true });
const second = partieReducer(premier, { type: "CHOISIR", reponseId: 13, estCorrecte: true });

expect(second).toBe(premier);
expect(second.score).toBe(1);
});

Simuler fetch​

Un test ne doit jamais dépendre du réseau : il serait lent et imprévisible. On remplace fetch par une fausse version qui renvoie ce qu'on veut :

import { vi } from "vitest";

vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({ ok: true, status: 200, json: () => Promise.resolve([questionValide]) }),
);

Après le test, on restaure les vraies fonctions avec vi.unstubAllGlobals() et vi.restoreAllMocks() (dans un afterEach).

Tester un composant comme un utilisateur​

Testing Library encourage à tester ce que voit et fait l'utilisateur, pas le code interne : on cherche un champ par son label, un bouton par son nom, un message par son rôle.

render(<FormulaireDemarrage onDemarrer={onDemarrer} />);

await userEvent.type(screen.getByLabelText("Pseudo"), "ab");
await userEvent.click(screen.getByRole("button", { name: "Commencer" }));

expect(screen.getByRole("alert")).toHaveTextContent("entre 3 et 20 caractères");
expect(onDemarrer).not.toHaveBeenCalled();
  • vi.fn() crée une fonction espion : elle enregistre ses appels ;
  • les actions de userEvent sont asynchrones : on les await ;
  • si les label et les role="alert" étaient oubliés, ces tests échoueraient : l'accessibilité facilite aussi les tests.

Un composant qui utilise le routeur ou le contexte est testé à l'intérieur de MemoryRouter (un routeur en mémoire, avec l'adresse de départ de votre choix) et de JoueurProvider.

Bonne pratique - Un test par comportement

Chaque it vérifie une règle, avec un titre qui la décrit. Quand un test échoue, son titre indique tout de suite ce qui ne va plus.


Test de mémorisation/compréhension​


Quelle fonction de Vitest regroupe plusieurs tests liés ?


Que vérifie expect(calculerPourcentage(7, 10)).toBe(70) ?


Pourquoi simule-t-on fetch dans un test ?


Comment Testing Library recommande-t-il de chercher un champ de formulaire ?


À quoi sert vi.fn() dans un test ?


Pourquoi tester le double clic sur le reducer plutôt que dans le navigateur ?



TP pour réfléchir et résoudre des problèmes​

Votre mission : écrire une suite de tests qui protège les règles importantes du quiz.

Étape 1 — Installer et configurer​

Installez les paquets, modifiez vite.config.ts, créez src/setupTests.ts et ajoutez le script test dans package.json.


Bonne pratique - Le test dans l'intégration continue

Une commande unique (npm test) permet de lancer tous les tests, à la main ou automatiquement avant chaque mise en ligne. C'est pourquoi on évite le mode « surveillance » dans le script (vitest run s'arrête quand c'est fini).

Étape 2 — Tester le score​

Créez src/logic/score.test.ts.


Bonne pratique - Tester les valeurs limites

Les bugs se cachent aux frontières : 49 et 50, 79 et 80, zéro question. Testez toujours « juste avant » et « juste après » chaque seuil.

Étape 3 — Tester le reducer​

Créez src/logic/partieReducer.test.ts. Complétez le test du double clic.


Bonne pratique - Vérifier qu'on ne modifie rien

Le test « ne modifie jamais l'état reçu » compare l'état avant et après avec toEqual : si quelqu'un écrit un jour etat.score++ dans le reducer, ce test l'attrape.

Étape 4 — Tester le chargement sans réseau​

Créez src/api/questions.test.ts. Complétez la fausse version de fetch.


Bonne pratique - Restaurer après chaque test

afterEach remet fetch et console.warn dans leur état d'origine : un test ne doit jamais laisser de traces qui influencent le suivant.

Étape 5 — Tester le formulaire comme un utilisateur​

Créez src/components/FormulaireDemarrage.test.tsx. Complétez la saisie.


Bonne pratique - Chercher comme un utilisateur

Le test trouve le champ par son label et le bouton par son nom : il vérifie ainsi, au passage, que le formulaire est accessible. Chercher par className casserait au premier changement de style.

Étape 6 — Tester une page protégée contre les adresses truquées​

Créez src/pages/PageResultat.test.tsx. Complétez l'affichage d'un résultat dans un routeur en mémoire.


Bonne pratique - Tester les cas d'attaque

Ce second test prouve que l'application résiste à une adresse truquée. Un test de sécurité est un test comme un autre : il décrit un comportement attendu face à une donnée hostile.

📌 Une solution

Ce qu'il faut retenir​

NotionRésumé
Test automatiqueUn programme qui vérifie un autre programme, rejouable en une seconde.
describe / it / expectRegrouper, décrire un comportement, vérifier une valeur.
Fonctions pures et reducerLes plus faciles à tester : état et action en entrée, résultat en sortie.
vi.stubGlobal / vi.fn()Simuler fetch et espionner les appels, sans réseau.
Testing LibraryTester un composant comme un utilisateur (label, rôle, texte).
MemoryRouterTester une page qui dépend du routeur, avec l'adresse de son choix.
cleanupNettoyer l'écran entre deux tests.

Aperçu de la prochaine séance​

Vos tests passent : vous pouvez maintenant livrer sereinement. Dans la prochaine séance, vous allez construire l'application pour la production, comprendre ce qui change par rapport au serveur de développement, gérer les variables d'environnement (et ne jamais y mettre de secret) et préparer l'hébergement.