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, withMagicMock
через 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.
Проверьте себя
1. Что задаёт атрибут return_value у Mock?
AИмя мока
BЗначение, которое вернёт вызов мока
CЧисло вызовов
DТип ошибки
2. Чем MagicMock отличается от Mock?
AНичем
BMagicMock дополнительно поддерживает магические методы (__len__, __getitem__, __enter__)
CMock быстрее
DMagicMock нельзя вызывать
3. Что вернёт mock.call_count?
AАргументы последнего вызова
BСколько раз мок был вызван
CTrue/False
DИмя метода
Поддержать проект