Как мокировать зависимости в тестах?
Что проверяют
Понимаете ли вы, зачем изолировать код от базы, сети и времени, и умеете ли делать это без хрупких тестов. Следом обычно спрашивают, почему patch «не сработал» и чем мок отличается от фейка.
Как отвечать
Сначала архитектура, потом инструменты. Если зависимость приходит через конструктор или аргумент, как UserService(user_repo), в тест просто передают заглушку, и patch не нужен вовсе. Патчить приходится то, что код импортирует и создаёт сам.
Инструменты стандартной библиотеки unittest.mock:
MockиMagicMock: объект, который принимает любые вызовы и запоминает их.MagicMockдополнительно поддерживает магические методы вроде__len__и__enter__;return_valueиside_effect: первое задаёт результат, второе позволяет выбросить исключение, вернуть последовательность значений или вызвать функцию;patch: подменяет имя на время теста как декоратор или контекстный менеджер. Правило: патчат там, где имя ищется, а не где объявлено. Еслиservices.pyделаетfrom repo import fetch_user, патчить надоservices.fetch_user;autospec=Trueилиcreate_autospec: мок повторяет сигнатуру настоящего объекта и падает на несуществующем методе или неверном числе аргументов;AsyncMock: для корутин, с Python 3.8patchсам подставляет его дляasync def;- проверки:
assert_called_once_with,call_args_list,assert_not_called.
В pytest то же самое удобнее через фикстуру monkeypatch или mocker из pytest-mock. Для HTTP есть responses и respx, для времени freezegun или time-machine, для базы часто лучше фейк: репозиторий в памяти или настоящая база в контейнере.
Где спотыкаются
Патчат по месту определения. Модуль уже импортировал функцию в своё пространство имён, подмена оригинала его не касается, и тест ходит в настоящую сеть.
Используют голый Mock без спецификации. Метод переименовали, а тест зелёный, потому что мок охотно отвечает на любое имя.
Мокают всё подряд и проверяют порядок внутренних вызовов. Такие тесты ломаются от любого рефакторинга и ничего не говорят о поведении, а связку с настоящей базой не проверяет никто.