Aller au contenu
Développement medium

Mock en Python : simuler des dépendances dans vos tests

9 min de lecture

logo python

Un mock est un objet qui imite une dépendance réelle pour isoler le code testé. Quand une fonction appelle une API, une base de données ou lit un fichier, on ne veut pas que le test dépende du réseau ou d'un service externe : on remplace cette dépendance par un mock qui renvoie une valeur contrôlée. Ce guide couvre le module standard unittest.mock (Mock, MagicMock, patch), la fixture monkeypatch de pytest et le plugin pytest-mock, sur un cas concret : simuler un appel requests sans toucher au réseau. Tout le code a été exécuté avec Python 3.12 et pytest 9.1.

Il s'adresse aux personnes à l'aise avec pytest qui veulent des tests rapides, déterministes et capables de vérifier les cas d'erreur difficiles à provoquer en vrai.

  • Comprendre ce qu'est un mock et quand l'utiliser.
  • Créer des faux objets avec Mock et MagicMock.
  • Remplacer une dépendance avec patch, au bon endroit.
  • Utiliser monkeypatch et pytest-mock dans vos tests.
  • Simuler une API et ses erreurs sans dépendre du réseau.

Un bon test unitaire vérifie une seule unité de code, en isolation. Si votre fonction appelle une API météo, un test qui tape réellement l'API devient lent, fragile (il échoue si le réseau tombe) et non déterministe (la valeur change). Pire, certains cas sont presque impossibles à provoquer en vrai : un timeout, une erreur 500, une réponse malformée.

Le mock résout tout cela en remplaçant la dépendance par un objet que vous contrôlez. Vous décidez ce qu'il renvoie, vous simulez une panne à la demande, et le test s'exécute en millisecondes sans réseau. La règle : on mocke ce qui est hors du périmètre testé (appels externes, horloge, aléatoire), pas la logique que le test doit précisément valider.

Le module standard unittest.mock fournit des objets qui acceptent n'importe quel appel et enregistrent comment ils ont été utilisés. Un MagicMock va plus loin que Mock en supportant aussi les méthodes spéciales (len(), itération, etc.).

from unittest.mock import MagicMock
service = MagicMock()
service.get_utilisateur.return_value = {"nom": "Alice"}
resultat = service.get_utilisateur(42)
assert resultat == {"nom": "Alice"}
service.get_utilisateur.assert_called_once_with(42)

Deux attributs structurent tout mock :

  • return_value : ce que le mock renvoie quand on l'appelle.
  • side_effect : une exception à lever ou une fonction à exécuter à la place, pour simuler une erreur ou un comportement dynamique.

Et des méthodes d'assertion vérifient les interactions : assert_called_once_with(...), assert_called(), assert_not_called(), plus l'attribut call_count.

Créer un mock ne suffit pas : il faut l'injecter à la place du vrai objet. C'est le rôle de patch, qui remplace temporairement une cible pendant la durée du test, puis la restaure automatiquement. On l'utilise en décorateur ou en gestionnaire de contexte.

from unittest.mock import patch
@patch("mon_module.requests.get")
def test_appel(mock_get):
mock_get.return_value.json.return_value = {"temp": 21}
# ... le test ...

Prenons un module qui interroge une API météo avec requests :

meteo.py
import requests
def temperature(ville):
r = requests.get(f"https://api.exemple.com/meteo/{ville}", timeout=5)
r.raise_for_status()
return r.json()["temp"]

On veut tester temperature() sans appeler l'API. On patche meteo.requests.get et on configure la réponse simulée :

test_meteo.py
from unittest.mock import patch
import meteo
@patch("meteo.requests.get")
def test_temperature(mock_get):
# la réponse simulée : r.json() renvoie {"temp": 21}
mock_get.return_value.json.return_value = {"temp": 21}
mock_get.return_value.raise_for_status.return_value = None
assert meteo.temperature("lyon") == 21
# on vérifie l'URL et le timeout réellement utilisés
mock_get.assert_called_once_with(
"https://api.exemple.com/meteo/lyon", timeout=5
)

Le test valide la logique (construction de l'URL, extraction de temp) sans dépendre d'un service externe. Il s'exécute en quelques millisecondes et donne toujours le même résultat.

Le vrai intérêt des mocks apparaît pour tester les pannes. Avec side_effect, le mock lève une exception au lieu de renvoyer une valeur, ce qui simule un réseau coupé sans avoir à débrancher quoi que ce soit :

import pytest
import meteo
@patch("meteo.requests.get", side_effect=ConnectionError("réseau indisponible"))
def test_temperature_reseau_coupe(mock_get):
with pytest.raises(ConnectionError):
meteo.temperature("lyon")

Vous vérifiez ainsi que votre code réagit correctement à une erreur impossible à déclencher de façon fiable en conditions réelles.

Si vous écrivez vos tests avec pytest, deux alternatives à patch s'offrent à vous, souvent plus lisibles.

La fixture monkeypatch est intégrée à pytest et remplace un attribut le temps du test, sans décorateur ni import :

def test_temperature(monkeypatch):
faux_reponse = MagicMock()
faux_reponse.json.return_value = {"temp": 5}
monkeypatch.setattr(meteo.requests, "get", lambda *a, **k: faux_reponse)
assert meteo.temperature("paris") == 5

Le plugin pytest-mock ajoute la fixture mocker, qui enveloppe unittest.mock et nettoie automatiquement les patches à la fin du test :

def test_temperature(mocker):
mock_get = mocker.patch("meteo.requests.get")
mock_get.return_value.json.return_value = {"temp": 30}
assert meteo.temperature("nice") == 30

Le mocking est puissant, mais mal dosé il produit des tests trompeurs.

  • Patcher au mauvais endroit : toujours la cible là où elle est utilisée (voir l'avertissement plus haut). C'est de loin l'erreur la plus courante.
  • Sur-mocker : si vous mockez la logique que le test devrait vérifier, le test ne teste plus rien. Ne mockez que les dépendances externes.
  • Mock trop permissif : un MagicMock accepte n'importe quel appel, même une méthode qui n'existe pas sur le vrai objet. Utilisez autospec=True (patch(..., autospec=True)) pour que le mock respecte la vraie signature et attrape les fautes de frappe.
  • Oublier de vérifier les interactions : un mock qui renvoie la bonne valeur ne prouve pas que votre code l'a appelé correctement. Ajoutez un assert_called_once_with(...) quand l'appel compte.
  • Un mock remplace une dépendance réelle pour rendre un test rapide, déterministe et isolé.
  • MagicMock crée un faux objet ; return_value fixe ce qu'il renvoie, side_effect simule une erreur.
  • patch injecte le mock à la place du vrai objet, en décorateur ou en context manager.
  • On patche là où l'objet est utilisé (mon_module.requests.get), jamais là où il est défini.
  • monkeypatch (pytest) et mocker (pytest-mock) sont des alternatives plus lisibles à patch.
  • side_effect permet de tester les pannes impossibles à provoquer en vrai.
  • Ne mockez que les dépendances externes et pensez à autospec=True pour des mocks fidèles.

Le mocking s'appuie sur un framework de test et sert surtout à isoler les appels externes comme les API.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn