Cours JavaScript et TypeScript · Module 11: Tests avec Jest
Pourquoi les tests sont-ils importants ?
Dans cette leçon4
Imagine Jurassic Park sans système de vérification de l'ADN des dinosaures. Chaque espèce fraîchement clonée pourrait cacher des défauts génétiques qui ne se révéleraient qu'au moment où un tyrannosaure s'échappe de son enclos. En programmation, les tests jouent exactement le même rôle : c'est un système d'alerte précoce qui attrape les bugs avant qu'ils n'atteignent les utilisateurs.
« On vérifie chaque échantillon avant que quoi que ce soit n'éclose », répète le Dr Rex, chef du laboratoire. Dans ce module tu vas construire ce genre de système de contrôle pour le code : un ensemble de tests qui vérifient tout seuls que les fonctions du parc font bien ce qu'elles promettent.
Ce que tu vas apprendre
- écrire des tests avec Jest et lire le rapport PASS/FAIL
- choisir les matchers comme des capteurs adaptés à une menace précise
- préparer et nettoyer l'enclos avant chaque test
- remplacer les dépendances dangereuses par des mocks inoffensifs
- tester du code asynchrone et faire avancer le temps avec une horloge factice
- écrire les tests avant le code avec la méthode TDD, et tester du TypeScript
Tests manuels vs tests automatisés
Tests manuels
Tester manuellement, c'est comme inspecter en personne chaque dinosaure du parc : c'est long, sujet à l'erreur humaine et impossible à répéter de façon identique. Prenons l'exemple de la fonction calculateDinosaurAge, qui calcule l'âge d'un spécimen à partir de son année d'éclosion. On la vérifie « à l'œil », en affichant les résultats dans la console :
1// Test manuel - tu dois vérifier le résultat toi-même
2function calculateDinosaurAge(birthYear) {
3 return 2024 - birthYear;
4}
5
6// Vérification manuelle dans la console :
7console.log(calculateDinosaurAge(2020)); // Est-ce bien 4 ? Je dois vérifier moi-même...
8console.log(calculateDinosaurAge(2015)); // Et là, 9 ? Je revérifie encore moi-même...Le code fonctionne, mais toute la vérification se passe dans ta tête : tu dois te rappeler que 2024 moins 2020 font 4. Rien n'est enregistré, donc après chaque modification de la fonction, l'inspection est à refaire depuis zéro.
Tests automatisés
Tester automatiquement, c'est comme installer des capteurs dans chaque enclos : ils fonctionnent 24 h/24, signalent immédiatement les problèmes et vérifient toujours les mêmes conditions. Dans le code, la fonction test donne un nom à la vérification, expect reçoit le résultat réel, et toBe le compare à la valeur attendue - tu découvriras les détails dans la leçon suivante.
1// Test automatisé - l'ordinateur vérifie pour toi
2function calculateDinosaurAge(birthYear) {
3 return 2024 - birthYear;
4}
5
6// Test automatisé :
7test("calcule l'âge du dinosaure", () => {
8 expect(calculateDinosaurAge(2020)).toBe(4);
9 expect(calculateDinosaurAge(2015)).toBe(9);
10 expect(calculateDinosaurAge(2024)).toBe(0);
11});
12// Résultat : PASS ou FAIL - tu le sais immédiatement !Les attentes sont désormais écrites dans le code : la vérification prend quelques millisecondes et donne chaque fois la même réponse. La fonction elle-même n'a pas changé - seul a changé celui qui surveille le résultat. Attention toutefois au piège : l'année 2024 est codée en dur à la fois dans la fonction et dans les attentes, donc les tests passeront encore en 2030, quand la fonction sous-estimera déjà l'âge. Un test ne vérifie que ce que tu lui demandes.
Types de tests
La pyramide des tests
Dans le monde des tests, il existe le concept de pyramide des tests. À sa base se trouvent les tests unitaires (unit tests), au milieu les tests d'intégration (integration tests), et au sommet les tests de bout en bout (E2E). Ce modèle a été popularisé par Mike Cohn dans son livre Succeeding with Agile. Dans l'exemple ci-dessous, getSpecies transforme une abréviation en nom complet d'espèce, getDinosaur et feedDinosaur sont deux modules du parc qui coopèrent, et page est un navigateur piloté par un outil de tests E2E - les méthodes goto, click et fill viennent de Playwright.
1// 1. Tests unitaires (Unit Tests) - testent une seule fonction
2// Les plus rapides, les moins chers, c'est d'eux qu'il faut le plus
3test("vérifie l'espèce du dinosaure", () => {
4 expect(getSpecies('T-Rex')).toBe('Tyrannosaurus Rex');
5});
6
7// 2. Tests d'intégration (Integration Tests) - testent la coopération des modules
8// Ils vérifient que les modules communiquent correctement entre eux
9test("le système d'alimentation coopère avec la base des dinosaures", () => {
10 const dino = getDinosaur('Rex-001');
11 const result = feedDinosaur(dino, 'meat');
12 expect(result.status).toBe('fed');
13 expect(dino.hunger).toBe(0);
14});
15
16// 3. Tests E2E (End-to-End) - testent toute l'application
17// Les plus lents, les plus chers, c'est d'eux qu'il faut le moins
18test('un utilisateur peut réserver une visite du parc', async () => {
19 await page.goto('/booking');
20 await page.click('#select-date');
21 await page.fill('#name', 'John Hammond');
22 await page.click('#submit');
23 expect(await page.textContent('.confirmation')).toContain('Réservation confirmée');
24});Les trois tests se terminent par une assertion, mais leur portée diffère. Un test unitaire ne touche ni à la base de données ni au réseau : il dure une fraction de milliseconde et désigne tout de suite la fonction coupable. Un test E2E lance un navigateur et toute l'application - c'est lui qui se rapproche le plus d'un vrai visiteur, mais quand il échoue, tu sais seulement que le problème se trouve quelque part sur le trajet entre la billetterie et la réservation.
Les fondations et le sommet de la pyramide
Sous la pyramide, on dessine souvent des fondations : l'analyse statique. Un linter (par exemple ESLint) et le système de types de TypeScript vérifient le code avant même que tu lances le moindre test, et ils attraperont une faute de frappe ou un texte passé à la place d'un nombre. Au sommet, au-dessus des tests E2E, il reste un peu de tests manuels, dits exploratoires : un humain y cherche des comportements étranges que personne n'avait prévus. L'ordre complet depuis la base est donc : analyse statique, tests unitaires, tests d'intégration, tests E2E et tests exploratoires. Des trois étages classiques, ce sont les tests unitaires les plus nombreux.
Proportions des tests
Combien de tests de chaque type ? En 2015, le Google Testing Blog a proposé cette répartition comme bon point de départ :
- 70 % de tests unitaires - rapides, précis, faciles à écrire
- 20 % de tests d'intégration - vérifient la coopération entre modules
- 10 % de tests E2E - simulent un véritable utilisateur
C'est une heuristique, pas une loi de la nature - les auteurs soulignent eux-mêmes que chaque équipe choisira ses propres chiffres, du moment que la forme de pyramide est conservée. La pyramide inversée, pleine de tests E2E lents et de clics manuels, est un antipattern bien connu, appelé le cornet de glace (ice cream cone).
Les avantages des tests
Les tests ne sont pas une perte de temps : c'est un investissement. Voici leurs principaux avantages :
- Détecter les bugs tôt - plus tôt tu trouves un bug, moins il coûte cher à corriger
- Documenter le comportement - les tests décrivent comment le code doit fonctionner
- Refactoriser en toute sécurité - tu peux modifier le code en sachant que les tests attraperont les régressions
- Déployer en confiance - des tests verts signifient que c'est prêt pour la production
Mon conseil : commence par des tests unitaires pour de petites fonctions pures, comme calculateDinosaurAge. Ce sont les moins chers à écrire et ceux qui te remboursent le plus vite en bugs attrapés. Dès la leçon suivante, tu découvriras Jest, l'outil qui exécute ce genre de tests.
Dans le laboratoire ci-dessous tourne un mini-runner de tests écrit en JavaScript pur. Regarde bien le dernier test, marqué « bug » : il passe, alors qu'il surveille un âge négatif. Un test vert n'est jamais plus malin que l'attente que tu y as écrite.
Retiens : un test est un capteur dans l'enclos - une fois installé, il surveille le dinosaure à chaque modification du code, jour et nuit.
Code de cette leçon : index.js
1// Tests JS/TS - Pourquoi les tests sont-ils importants ?
2console.log("=== Jurassic Park - Système de Vérification de l'ADN ===\n");
3
4// Tests manuels vs automatisés
5
6// EXEMPLE 1 : Test manuel (problématique)
7function calculateDinosaurAge(birthYear) {
8 return 2024 - birthYear;
9}
10
11console.log("--- Test manuel ---");
12console.log("Âge (2020) :", calculateDinosaurAge(2020)); // Je vérifie moi-même : est-ce 4 ?
13console.log("Âge (2015) :", calculateDinosaurAge(2015)); // Je vérifie moi-même : est-ce 9 ?
14console.log("Âge (2024) :", calculateDinosaurAge(2024)); // Je vérifie moi-même : est-ce 0 ?
15
16// EXEMPLE 2 : Test automatisé (fiable)
17function autoTest(description, actual, expected) {
18 const passed = actual === expected;
19 const icon = passed ? "PASS" : "FAIL";
20 console.log(`[${icon}] ${description} : obtenu ${actual}, attendu ${expected}`);
21 return passed;
22}
23
24console.log("\n--- Test automatisé ---");
25autoTest("Âge du dinosaure né en 2020", calculateDinosaurAge(2020), 4);
26autoTest("Âge du dinosaure né en 2015", calculateDinosaurAge(2015), 9);
27autoTest("Âge du dinosaure né en 2024", calculateDinosaurAge(2024), 0);
28
29// EXEMPLE 3 : Pyramide des tests
30console.log("\n--- Pyramide des tests ---");
31console.log("Base : Tests unitaires (70%) - rapides, précis");
32console.log("Milieu : Intégration (20%) - coopération des modules");
33console.log("Sommet : Tests E2E (10%) - application entière");
34
35// Simulation d'un test runner simple
36console.log("\n--- Mini Test Runner ---");
37
38function runTests(suiteName, tests) {
39 console.log(`\nSuite : ${suiteName}`);
40 let passed = 0;
41 let failed = 0;
42
43 tests.forEach(({ name, fn }) => {
44 try {
45 fn();
46 console.log(` [PASS] ${name}`);
47 passed++;
48 } catch (e) {
49 console.log(` [FAIL] ${name} : ${e.message}`);
50 failed++;
51 }
52 });
53
54 console.log(` Résultats : ${passed} réussis, ${failed} échoués`);
55}
56
57function assertEqual(actual, expected) {
58 if (actual !== expected) {
59 throw new Error(`Attendu ${expected}, obtenu ${actual}`);
60 }
61}
62
63runTests("DinosaurAge", [
64 {
65 name: "calcule l'âge correctement",
66 fn: () => assertEqual(calculateDinosaurAge(2020), 4)
67 },
68 {
69 name: "gère l'année courante",
70 fn: () => assertEqual(calculateDinosaurAge(2024), 0)
71 },
72 {
73 name: "gère un âge négatif (bug !)",
74 fn: () => assertEqual(calculateDinosaurAge(2025), -1)
75 }
76]);Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Quelle couche de la pyramide des tests contient le plus de tests ?