Le test échoue… parce que l'API météo est en panne
Tu écris une fonction d'alerte : si la température dépasse 35 °C, elle envoie une notification. Ce matin, tu lances le test. Il passe au rouge. Panique. Tu inspectes le code pendant vingt minutes. Le code est parfait. C'est l'API météo externe qui était en panne.
Ton test ne testait pas ta logique d'alerte. Il testait l'état du réseau. Ce n'est pas un vrai échec de ton code : c'est un faux positif.
Test instable (« flaky ») : un test qui dépend du réseau, de la base de données ou de l'heure système n'est pas fiable. Il peut passer le matin et échouer l'après-midi sans que tu aies changé une seule ligne. On appelle ça un test « flaky », et c'est l'une des causes #1 d'abandon des suites de tests dans les équipes.
La solution : remplacer les dépendances instables par des doublures, des valeurs fixes et contrôlées que tu fournis toi-même le temps du test.
Stub, mock : deux types de doublures
On parle de « doublures de test » (en anglais : test doubles) pour désigner tout ce qui remplace une vraie dépendance pendant un test. Les deux plus courants :
Stub : une doublure qui remplace une dépendance par une valeur fixe et contrôlée. Tu fournis au code une fausse source de données (un faux taux de change, une fausse réponse d'API). Le stub ne vérifie rien : il se contente de renvoyer ce que tu lui demandes.
Mock : une doublure qui fait la même chose qu'un stub, mais qui vérifie en plus qu'un appel a bien eu lieu. Exemple : « la fonction d'envoi d'email a-t-elle été appelée exactement une fois, avec le bon destinataire ? » Si l'appel n'a pas eu lieu, le test échoue.
Pour pouvoir remplacer une dépendance par une doublure, encore faut-il pouvoir la passer en paramètre. C'est le principe de l'injection de dépendance : plutôt que d'appeler l'API directement dans le corps de ta fonction, tu la reçois en argument. La fonction ne sait pas si c'est la vraie API ou une doublure. Elle appelle juste ce qu'on lui a donné.
Une fonction qui reçoit ses dépendances en paramètre est testable par définition. Une fonction qui appelle directement fetch(), new Date() ou Math.random() en dur est difficile à tester : tu ne peux pas contrôler ce qu'elles renvoient.
À toi : corrige le calcul avec le stub au vert
Voici une fonction prixTTC qui reçoit sa source de taux en paramètre : c'est l'injection de dépendance. Dans les tests, on lui passe un stub (() => 0.20) à la place d'une vraie API. Mais le calcul contient un bug. Lance les tests : un est rouge. Lis le message, corrige le code, relance.
Le test passe un stub () => 0.20 à la place d'une vraie API de taux. Pourquoi utiliser une doublure ici plutôt que la vraie source du taux ? Écris ta réponse avant de dérouler.
Voir la réponse
Pour que le test vérifie uniquement le calcul de prixTTC, de façon rapide et déterministe, sans dépendre d'une API (lente, parfois en panne) ni d'une config qui pourrait changer. Le stub fige l'entrée (0.20) pour que seul ton code soit jugé. Si le test échoue, tu sais que le bug est dans ta fonction, pas dans le réseau.
Bloqué sur la correction ? Voir le fix
Le taux s'applique au prix, pas en valeur absolue. Remplace prixHT + getTaux() par prixHT + prixHT * getTaux(). La ligne devient return prixHT + prixHT * getTaux();. Avec le stub à 20 %, ça donne 100 + 100 * 0.20 = 120. Avec le stub à 0, ça donne 100 + 0 = 100. Les deux tests passent au vert. Le stub a fait son travail : il a isolé le bug dans ton calcul, pas dans le réseau.
La règle d'or : mocker le moins possible
Les doublures sont utiles, mais elles ont un prix : elles remplacent la réalité par quelque chose de fabriqué. Plus tu mockes, plus tes tests ressemblent à un théâtre où tout le monde joue un rôle et personne ne fait vraiment son travail.
Ce qu'on a le droit de mocker : uniquement les entrées/sorties externes, c'est-à-dire le réseau (fetch), la base de données, l'horloge (Date), le hasard (Math.random), le système de fichiers. Ces dépendances sont lentes, instables, ou non déterministes : les remplacer rend le test rapide et fiable.
Ce qu'on ne doit jamais mocker : sa propre logique métier. Si tu mockes la fonction que tu es censé tester, le test ne teste plus rien de réel : il valide tes doublures. C'est le piège du « sur-mock », dénoncé notamment dans le débat « Is TDD dead ? » (leçon 6) : des suites de tests qui passent au vert alors que le code est cassé, parce que tout ce qui pourrait échouer a été remplacé par une doublure.
La règle pratique : si tu te retrouves à mocker une fonction pour tester une autre fonction qui l'appelle, demande-toi si la première est vraiment une dépendance externe. Sinon, teste le tout ensemble avec de vraies données. Les mocks répondent à la question « l'appel a-t-il eu lieu ? » ; les vrais tests répondent à « le résultat est-il juste ? »
The test fails… because the weather API is down
You write an alert function: if the temperature exceeds 35 °C, it sends a notification. You run the test this morning — it goes red. Panic. You inspect the code for twenty minutes. The code is fine. The external weather API was down.
Your test wasn't testing your alert logic. It was testing the state of the network. That's not a real failure in your code: it's a false positive.
Watch out — flaky test: a test that depends on the network, the database, or the system clock is not reliable. It can pass in the morning and fail in the afternoon without you changing a single line. This is called a "flaky" test — and it's one of the #1 reasons teams abandon test suites.
The solution: replace the unstable dependencies with doubles — fixed, controlled values you supply yourself for the duration of the test.
Stub, mock: two kinds of doubles
We use the term "test doubles" to describe anything that replaces a real dependency during a test. The two most common kinds:
Stub: a double that replaces a dependency with a fixed, controlled value. You give the code a fake data source (a fake exchange rate, a fake API response). The stub verifies nothing — it just returns what you tell it to.
Mock: a double that does the same as a stub, but also verifies that a call actually happened. Example: "was the email-sending function called exactly once, with the right recipient?" If the call didn't happen, the test fails.
To be able to replace a dependency with a double, you need to be able to pass it as a parameter. That's the principle of dependency injection: rather than calling the API directly inside your function body, you receive it as an argument. The function doesn't know whether it's the real API or a double — it just calls whatever it's been given.
A function that receives its dependencies as parameters is testable by definition. A function that calls fetch(), new Date(), or Math.random() directly is hard to test — you can't control what they return.
Your turn: fix the calculation and get the stub to green
Here's a priceWithTax function that receives its rate source as a parameter — that's dependency injection. In the tests, we pass it a stub (() => 0.20) instead of a real API. But the calculation has a bug. Run the tests: one is red. Read the message, fix the code, rerun.
The test passes a stub () => 0.20 instead of a real rate API. Why use a double here rather than the real rate source? Write your answer before expanding.
Show the answer
So the test checks only the calculation in priceWithTax, quickly and deterministically — without depending on an API (slow, sometimes down) or a config that could change. The stub freezes the input (0.20) so only your code is being judged. If the test fails, you know the bug is in your function, not in the network.
Stuck on the fix? Show it
The rate applies to the price, not as an absolute value. Replace netPrice + getRate() with netPrice + netPrice * getRate(). The line becomes return netPrice + netPrice * getRate();. With the 20% stub that gives 100 + 100 * 0.20 = 120. With the 0 stub, 100 + 0 = 100. Both tests go green. The stub did its job: it isolated the bug in your calculation, not the network.
The golden rule: mock as little as possible
Doubles are useful — but they have a cost: they replace reality with something fabricated. The more you mock, the more your tests resemble a theatre where everyone plays a part and nobody actually does any real work.
What you're allowed to mock: only external I/O — the network (fetch), the database, the clock (Date), randomness (Math.random), the file system. These dependencies are slow, unstable, or non-deterministic: replacing them makes tests fast and reliable.
What you must never mock: your own business logic. If you mock the function you're supposed to be testing, the test no longer tests anything real — it validates your doubles. This is the "over-mocking" trap, called out in the "Is TDD dead?" debate (lesson 6): test suites that go green while the code is broken, because everything that could fail has been replaced by a double.
The practical rule: if you find yourself mocking a function to test another function that calls it, ask whether the first one is truly an external dependency. If not, test the whole thing together with real data. Mocks answer "did the call happen?"; real tests answer "is the result correct?"
🎯 Pratique
S'entraîner (clique pour ouvrir) :
💬 Ré-explique sans regarder
Avec tes mots : c'est quoi un stub, un mock, et pourquoi isoler la fonction testée de ses dépendances ?
🧠 Rappel libre
Sans remonter : qu'est-ce qu'on a le droit de mocker, et qu'est-ce qu'on ne doit jamais mocker ?
Date), aléatoire (Math.random), système de fichiers. On ne mocke jamais la logique métier qu'on est censé tester. Sinon le test ne vérifie plus rien de réel : il valide tes doublures, pas ton code.⚖️ Juge le code de l'IA
Pour tester calculerTotal(panier), l'IA propose de mocker calculerTotal elle-même pour qu'elle renvoie 25, puis vérifie que ça vaut 25. Le test passe au vert. Tu acceptes, ou tu rejettes ?
calculerTotal pour qu'elle renvoie 25, puis vérifier que ça vaut 25, c'est tautologique. On teste calculerTotal en l'appelant pour de vrai avec de vraies données de panier ; on ne mocke que ses éventuelles dépendances externes (I/O), pas elle.Tu sais maintenant isoler une unité et mocker les bonnes dépendances. Mais tout ça vit encore dans notre bac à sable. Dans TON projet, qui lance les tests ? La prochaine leçon installe un vrai framework, Vitest, et lance tes tests pour de bon, en watch et en CI.
Leçon 8 : Tes tests dans un vrai projet →