Mock и MagicMock
Знакомимся с unittest.mock: объекты Mock и MagicMock.
Mock — универсальный поддельный объект из стандартной библиотеки: он принимает любые вызовы и обращения к атрибутам и запоминает их.
Mock притворяется чем угодно
Объект Mock не требует описывать структуру заранее. У него «есть» любой атрибут и любой метод — он создаёт их на лету. Это удобно для подмены зависимостей: не нужно писать класс-заглушку, как в прошлом уроке, — Mock сам станет любым объектом.
from unittest.mock import Mock
# return_value задаёт, что вернёт вызов мока
service = Mock(return_value=42)
print(service()) # вызов вернёт 42
print(service.called) # был ли вызван?
print(service.call_count) # сколько раз вызван
print(service.call_args) # с какими аргументами в последний раз
Вывод:
42 True 1 call()
return_value: что вернуть
Атрибут return_value задаёт результат вызова мока. Можно задать при создании или позже. Любой метод мока тоже мок — и ему тоже можно назначить return_value:
from unittest.mock import Mock
api = Mock()
api.get_user.return_value = {"id": 1, "name": "Анна"}
user = api.get_user(1)
print(user)
print(user["name"])
print("Метод вызван:", api.get_user.called)
Вывод:
{'id': 1, 'name': 'Анна'}
Анна
Метод вызван: True
Мы не создавали класс API — api.get_user возник сам, и мы лишь сказали, что он должен вернуть. Это и есть «заглушка на лету».
Mock против MagicMock
MagicMock — это Mock, который вдобавок поддерживает «магические» методы Python (__len__, __iter__, __getitem__, __enter__ и т.д.). Поэтому он умеет притворяться объектом, который индексируют, по которому итерируют или используют в with. Обычный Mock на такие операции выдал бы ошибку.
from unittest.mock import MagicMock
# MagicMock умеет len(), [] и итерацию
m = MagicMock()
m.__len__.return_value = 3
m.__getitem__.return_value = "элемент"
print(len(m)) # 3 — работает благодаря MagicMock
print(m[0]) # 'элемент'
print(bool(m)) # MagicMock по умолчанию истинный
Вывод:
3 элемент True
Какой выбрать
| Ситуация | Что брать |
| обычная подмена функции/метода | Mock |
нужны len(), [], for, with | MagicMock |
через patch (см. далее) | MagicMock по умолчанию |
На практике многие просто всегда берут MagicMock — он умеет всё, что Mock, и сверх того. А patch (следующий урок) и так подставляет MagicMock автоматически.
Атрибуты-«вопросы» к моку
После того как код поработал с моком, у мока можно спросить, как его использовали:
.called— был ли вызван (True/False)..call_count— сколько раз..call_args— аргументы последнего вызова..call_args_list— аргументы всех вызовов.
Эти «вопросы» — основа проверки взаимодействий, о которой пойдёт речь в уроке про assert_called_with.
Опасность «слишком умного» мока
У гибкости Mock есть обратная сторона: он отвечает на любой атрибут и метод, даже на тот, которого у настоящего объекта нет. Из-за этого тест может пройти на вызове mock.savee(...) с опечаткой, хотя в реальности такого метода не существует. Чтобы подстраховаться, используют Mock(spec=RealClass) или create_autospec: тогда мок разрешает только те атрибуты и методы, что есть у реального класса, а на опечатку сразу даст ошибку. В строгих тестах это спасает от ложно-зелёных результатов.
Где брать мок: создавать или патчить
Есть два способа подсунуть мок в код. Первый — создать Mock/MagicMock и передать его в функцию аргументом (как мы делали с почтовым сервисом). Это чисто и явно, и подходит, когда зависимость и так приходит снаружи. Второй — когда код жёстко импортирует и зовёт зависимость сам: тогда мок нужно подставить на место через patch, чему посвящён следующий урок. Запомните развилку: зависимость передаётся аргументом — создавайте мок руками; зависимость «зашита» внутри — патчите.
Итог
Mock— поддельный объект, который принимает любые вызовы и запоминает их.return_valueзадаёт, что вернёт вызов мока или его метода.MagicMockдополнительно поддерживает магические методы (len,[],with).- Мок помнит, как его использовали:
called,call_count,call_args.