Короткий ответ
Нужно патчить имя функции/клиента в пространстве имён модуля, который его импортировал и использует, а не в модуле, где он изначально объявлен — иначе патч не подействует на реально выполняемый вызов.
Как это работает подробнее
unittest.mock.patch подменяет атрибут объекта в конкретном пространстве имён (модуле) на время теста. Если модуль a делает from b import call_api и вызывает call_api(...), имя call_api существует и используется в пространстве имён модуля a — патчить нужно a.call_api, а не b.call_api, иначе ссылка в модуле a останется на оригинальную функцию.
Пример кода
testing_example_308.py
# service.py
from external_client import call_api
def get_price(product_id):
return call_api(f"/price/{product_id}")["price"]
# test_service.py
from unittest.mock import patch
def test_get_price_uses_api_response():
with patch("service.call_api") as mocked_call: # патчим там, где используется
mocked_call.return_value = {"price": 100}
assert get_price(42) == 100Что использовать на практике
- Практика. Для HTTP-вызовов часто удобнее использовать специализированные библиотеки (responses, httpx-mock, requests-mock), которые перехватывают запросы на уровне транспорта и позволяют описывать ожидаемые URL, метод, заголовки и тело ответа, а не мокать внутренние детали реализации конкретного клиента, что делает тест устойчивее к смене библиотеки.
Подводные камни
- Риск. Патчить модуль, где функция определена, вместо модуля, где она используется — классическая ошибка, из-за которой мок «не срабатывает», и тест либо падает с реальной сетевой ошибкой, либо незаметно делает реальный запрос во внешний сервис. Мокать слишком низкоуровневые детали (например, внутренний сокет или TCP-соединение), из-за чего тест становится хрупким к любому рефакторингу реализации клиента.