frontend / javascript
Tests avec Jest
Explication
Ce que vous allez apprendre
- Structurer une suite de tests avec
describe,testetexpect - Choisir entre
toBe(égalité stricte) ettoEqual(égalité de structure) - Isoler une fonction de ses dépendances externes avec des mocks
- Créer une fonction espionnée avec
jest.fn()et vérifier ses appels - Remplacer un module entier avec
jest.mock()pour tester sans dépendance réelle
Dans quel contexte ?
Une pull request ajoute une fonction calculerRemise(panier) avec un test expect(calculerRemise(panier)).toBe({ total: 90, remise: 10 }). Le test échoue systématiquement en CI avec un message cryptique, alors que la fonction semble correcte en la testant manuellement. Le problème : toBe compare par référence stricte (comme ===), donc deux objets au contenu identique mais à des adresses mémoire différentes ne sont jamais toBe-égaux. Remplacer toBe par toEqual, qui compare le contenu structurel, corrige immédiatement le test.
1. Pourquoi un programme qui "marche" ne suffit pas
Un programme qui "marche" au moment où on l'écrit ne garantit rien sur son comportement futur : chaque modification risque de casser silencieusement quelque chose ailleurs. Les tests automatisés forment un filet de sécurité qui se déclenche à chaque changement.
2. Le vocabulaire de base de Jest
Jest structure les tests autour de quelques mots-clés simples : describe regroupe des tests liés à une même fonctionnalité, test (ou it) définit un cas précis, et expect(...).toBe(...) formule l'attendu.
Prérequis
Aucune connaissance préalable de Jest n'est nécessaire, mais une bonne aisance avec les fonctions (leçon 2) et les Promises (leçon 11) aide à comprendre les tests de code asynchrone présentés plus loin dans cette leçon.
3. Un piège fréquent chez les débutants
toBe fait une égalité stricte, comme ===, adaptée aux valeurs primitives. toEqual fait une égalité de structure profonde, nécessaire pour comparer des objets — les confondre est une erreur très courante au début.
| Matcher | Compare | Pour quel type de valeur |
|---|---|---|
toBe | Référence stricte (===) | Nombres, chaînes, booléens |
toEqual | Structure profonde | Objets, tableaux |
toBeCloseTo | Valeur numérique approximative | Nombres flottants |
Piège fréquent
expect({ a: 1 }).toBe({ a: 1 }) échoue toujours, même si le contenu est identique : ce sont deux objets différents en mémoire. Utilisez toEqual dès que vous comparez un objet ou un tableau.
4. Isoler le code testé de ses dépendances
Un test unitaire efficace isole le code testé de ses dépendances externes — une base de données, une API réseau — grâce à des "mocks" : de fausses versions programmables de ces dépendances.
5. Les deux outils de Jest pour mocker
jest.fn() crée une fonction espionnée dont on peut vérifier les appels. jest.mock() remplace un module entier, pour tester la logique sans jamais dépendre d'un service réel, lent ou instable.
Cette leçon referme le premier grand bloc "pratique professionnelle" du cours avant d'aborder, dans les prochaines leçons, l'outillage qui transforme ce code en application livrée en production.
Commandes & code
Tests avec Jest
Écrire des suites de tests unitaires fiables en JavaScript.
// fichier : calculatrice.js
function additionner(a, b) {
return a + b;
}
function diviser(a, b) {
if (b === 0) throw new Error("Division par zero");
return a / b;
}
module.exports = { additionner, diviser };
// fichier : api.js
async function obtenirUtilisateur(id, client) {
const reponse = await client.get(`/utilisateurs/${id}`);
return reponse.data;
}
module.exports = { obtenirUtilisateur };// fichier : calculatrice.test.js
const { additionner, diviser } = require("./calculatrice");
// describe : regroupe des tests lies
describe("Calculatrice", () => {
// test / it : un cas de test individuel
test("additionne deux nombres positifs", () => {
expect(additionner(2, 3)).toBe(5);
});
it("gere les nombres negatifs", () => {
expect(additionner(-1, -1)).toBe(-2);
});
test("leve une erreur sur division par zero", () => {
expect(() => diviser(10, 0)).toThrow("Division par zero");
expect(() => diviser(10, 0)).toThrow(Error);
});
// beforeEach / afterEach : setup/teardown autour de CHAQUE test
let calculatriceAvecEtat;
beforeEach(() => {
calculatriceAvecEtat = { historique: [] };
});
afterEach(() => {
calculatriceAvecEtat = null;
});
// beforeAll / afterAll : setup/teardown UNE SEULE FOIS pour tout le describe
beforeAll(() => console.log("Debut de la suite Calculatrice"));
afterAll(() => console.log("Fin de la suite Calculatrice"));
// test.each : parametrer plusieurs cas sans dupliquer le code
test.each([
[2, 3, 5],
[-1, 1, 0],
[0, 0, 0],
[100, -50, 50],
])("additionner(%i, %i) doit valoir %i", (a, b, attendu) => {
expect(additionner(a, b)).toBe(attendu);
});
});
// --- Matchers courants ---
test("panorama des matchers", () => {
expect(2 + 2).toBe(4); // egalite stricte (===)
expect({ a: 1 }).toEqual({ a: 1 }); // egalite profonde de structure
expect([1, 2, 3]).toContain(2);
expect(0.1 + 0.2).toBeCloseTo(0.3); // pour les flottants
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect("texte").toMatch(/ex/);
expect(() => { throw new Error("x"); }).toThrow();
expect([1, 2, 3]).toHaveLength(3);
});
// --- Mocks : isoler le code teste ---
test("mock d'une fonction simple", () => {
const mockFn = jest.fn((x) => x * 2);
expect(mockFn(5)).toBe(10);
expect(mockFn).toHaveBeenCalledWith(5);
expect(mockFn).toHaveBeenCalledTimes(1);
});
test("mock avec valeur de retour controlee", () => {
const mockClient = { get: jest.fn() };
mockClient.get.mockResolvedValue({ data: { id: 1, nom: "Alice" } });
const { obtenirUtilisateur } = require("./api");
return obtenirUtilisateur(1, mockClient).then((utilisateur) => {
expect(utilisateur.nom).toBe("Alice");
expect(mockClient.get).toHaveBeenCalledWith("/utilisateurs/1");
});
});
// --- Mocker un module entier ---
jest.mock("./api");
// --- Tests asynchrones ---
test("test async avec async/await", async () => {
const client = { get: jest.fn().mockResolvedValue({ data: { id: 2 } }) };
const { obtenirUtilisateur } = require("./api");
const resultat = await obtenirUtilisateur(2, client);
expect(resultat.id).toBe(2);
});
// --- Spies : observer sans remplacer completement le comportement ---
test("spy sur console.error", () => {
const spy = jest.spyOn(console, "error").mockImplementation(() => {});
console.error("test");
expect(spy).toHaveBeenCalledWith("test");
spy.mockRestore(); // restaure le comportement original apres le test
});
// --- Snapshot testing : detecter des changements inattendus dans une structure ---
test("snapshot de configuration", () => {
const config = { theme: "sombre", langue: "fr" };
expect(config).toMatchSnapshot();
});npx jest # lance tous les tests
npx jest calculatrice # filtre par nom de fichier
npx jest --coverage # rapport de couverture de code
npx jest --watch # relance automatiquement au changement de fichierRésumé
describe/teststructurent les suites ;beforeEach/afterEachgèrent le setup/teardown répété.toBecompare par référence/valeur stricte,toEqualcompare la structure en profondeur.jest.fn()/jest.mock()isolent le code testé de ses dépendances externes (API, BDD, fichiers).- Le snapshot testing détecte les changements inattendus dans une structure de données ou un rendu.
Exercices pratiques
Mission : le test qui compare toEqual au lieu de toBe
Objectif : Choisir le bon matcher Jest selon le type de comparaison, et mocker une dépendance réseau sans jamais appeler la vraie API.
Contexte
Un développeur de Technologik écrit ce test qui échoue systématiquement, alors que la fonction testée semble correcte :
function creerUtilisateur(nom) {
return { nom, actif: true };
}
test("cree un utilisateur actif", () => {
expect(creerUtilisateur("Alice")).toBe({ nom: "Alice", actif: true }); // echoue !
});Un second test doit appeler obtenirUtilisateur(1, client), qui fait un vrai appel réseau via client.get(...), mais l'équipe veut que la suite de tests s'exécute en quelques millisecondes, sans jamais dépendre d'un serveur réel.