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,itetexpect; - tester une fonction pure, un reducer et une validation ;
- simuler
fetchpour 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...
- taper
/resultat?score=9&total=3et lire le message affiché - dire pourquoi le pseudo n'est plus dans l'adresse de la partie
- dire ce qui se passe, dans le hook
useJoueur, quand aucunJoueurProvidern'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 teste | Difficulté | Exemple du quiz |
|---|---|---|
| Une fonction pure | très facile | calculerPourcentage, lireNiveau |
| Un reducer | très facile | partieReducer : le double clic est ignoré |
Un chargement (avec fetch simulé) | moyen | chargerQuestions : 404, données invalides |
| Un composant, comme un utilisateur | moyen | FormulaireDemarrage : 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
| Paquet | Rôle |
|---|---|
vitest | lance les tests (même moteur que Vite) |
jsdom | simule un navigateur dans Node.js |
@testing-library/react | affiche un composant et le cherche comme un utilisateur |
@testing-library/user-event | simule la saisie et les clics |
@testing-library/jest-dom | ajoute des vérifications utiles (toBeInTheDocument...) |
Configurer
vite.config.ts : on importe defineConfig depuis vitest/config et on ajoute la section test.
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 :
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
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);
});
});
describeregroupe 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érification | Rô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
userEventsont asynchrones : on lesawait; - si les
labelet lesrole="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.
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
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.
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.
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.
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.
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.
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.
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
| Notion | Résumé |
|---|---|
| Test automatique | Un programme qui vérifie un autre programme, rejouable en une seconde. |
describe / it / expect | Regrouper, décrire un comportement, vérifier une valeur. |
| Fonctions pures et reducer | Les 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 Library | Tester un composant comme un utilisateur (label, rôle, texte). |
MemoryRouter | Tester une page qui dépend du routeur, avec l'adresse de son choix. |
cleanup | Nettoyer 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.