Lesson 7/9 11 min

Mocks and stubs

Isolate what you test: replace the database, network and clock with doubles. And the golden rule: mock as little as possible.

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.

Prédis avant de lire

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.

🧪 Labo de test · édite le code, lance, observe
Code à tester
Tests
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 ? »

À gauche, la fonction testée reliée à des dépendances réelles : API lente, base de données, horloge, instables. À droite, les mêmes remplacées par des doublures (stubs) : valeurs fixes, rapides et contrôlées. La logique réelle reste intacte dans les deux cas. DÉPENDANCES RÉELLES AVEC DOUBLURES (STUBS) Fonction testée logique réelle 🌐 API lente / instable 🗄️ Base de données lente / externe 🕒 Horloge non déterministe Fonction testée logique réelle stub API valeur fixe stub base rapide / contrôlé stub horloge déterministe
On remplace les I/O par des doublures : la logique réelle reste intacte et c'est elle qu'on juge.

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.

Predict before reading on

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.

🧪 Test lab · edit the code, run, observe
Code under test
Tests
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?"

On the left, the tested function connected to real dependencies: slow API, database, clock — unstable. On the right, the same replaced by doubles (stubs): fixed values, fast and controlled. The real logic stays intact in both cases. REAL DEPENDENCIES WITH DOUBLES (STUBS) Tested function real logic 🌐 API slow / unstable 🗄️ Database slow / external 🕒 Clock non-deterministic Tested function real logic API stub fixed value DB stub fast / controlled Clock stub deterministic
Replace I/O with doubles — the real logic stays intact and that's what's being judged.

🎯 Pratique

S'entraîner (clique pour ouvrir) :

💬 Ré-explique sans regarder
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 ?

Une bonne explication dit : un stub est une doublure qui renvoie une valeur fixe en remplacement d'une vraie dépendance ; un mock est une doublure qui vérifie en plus qu'un appel a bien eu lieu. On isole pour juger uniquement ta logique, sans dépendre d'un réseau ou d'une base lents et instables. Ça évite les faux positifs et rend le test rapide et déterministe.
🧠 Rappel libre
Rappel libre

Sans remonter : qu'est-ce qu'on a le droit de mocker, et qu'est-ce qu'on ne doit jamais mocker ?

On mocke les I/O externes : réseau, base de données, horloge (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
Accepter ou rejeter 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 ?

À rejeter. Elle mocke la fonction même qu'on veut tester : le test ne teste plus rien, il valide le mock, pas le calcul. Mocker 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.
Pourquoi remplacer une vraie API par un stub dans un test ?
Différence entre un stub et un mock ?
Que faut-il mocker, et quoi surtout pas ?
Quel est le danger du sur-mock (tout mocker) ?
Next step

You now know how to isolate a unit and mock the right dependencies. But all of that still lives in our sandbox. In YOUR project, who runs the tests? The next lesson installs a real framework, Vitest, and runs your tests for real, in watch mode and in CI.

Lesson 8: Your tests in a real project →
Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement