Какие основные методы используются для проверки вызовов Mock-объектов в unittest.mock?

Ответ

В библиотеке unittest.mock объекты Mock и MagicMock записывают все обращения к ним. Для проверки этих обращений используются специальные методы-утверждения (assertions).

Настройка поведения мока

Перед проверкой вызовов мок обычно настраивают:

  • mock.return_value: Атрибут, который определяет значение, возвращаемое при вызове мока.
  • mock.side_effect: Позволяет задать более сложное поведение: выбросить исключение, вернуть разные значения при последовательных вызовах или выполнить функцию.

Проверка вызовов (Assertions)

Основные методы для проверки взаимодействия с моком:

  • mock.assert_called(): Проверяет, что мок был вызван хотя бы один раз.
  • mock.assert_called_once(): Проверяет, что мок был вызван ровно один раз.
  • mock.assert_not_called(): Проверяет, что мок не вызывался ни разу.
  • mock.assert_called_with(*args, **kwargs): Проверяет, что последний вызов мока был с указанными аргументами.
  • mock.assert_called_once_with(*args, **kwargs): Объединяет assert_called_once() и assert_called_with().
  • mock.assert_any_call(*args, **kwargs): Проверяет, был ли хотя бы один вызов с указанными аргументами среди всех вызовов.
  • mock.call_count: Целочисленный атрибут, содержащий количество вызовов мока.

Практический пример:

Предположим, мы тестируем функцию, которая использует внешний API для отправки уведомлений.

# Код, который мы тестируем
def send_notification(api_client, user_id, message):
    if len(message) > 0:
        api_client.send(user_id, text=message)

# Тест для этого кода
from unittest.mock import Mock
import unittest

class TestNotification(unittest.TestCase):
    def test_send_notification_with_message(self):
        # 1. Arrange: Создаем мок для API клиента
        mock_api = Mock()

        # 2. Act: Вызываем нашу функцию с моком
        send_notification(mock_api, 123, "Hello!")

        # 3. Assert: Проверяем, что метод .send() был вызван один раз
        # и с правильными аргументами.
        mock_api.send.assert_called_once_with(123, text="Hello!")

    def test_send_notification_with_empty_message(self):
        # 1. Arrange
        mock_api = Mock()

        # 2. Act
        send_notification(mock_api, 123, "")

        # 3. Assert: Проверяем, что метод .send() не вызывался
        mock_api.send.assert_not_called()

if __name__ == '__main__':
    unittest.main(argv=['first-arg-is-ignored'], exit=False)

Ответ 18+ 🔞

Ага, смотри, вот тебе про эти ваши моки в питоне. Библиотека unittest.mock — это такая штука, которая записывает всё, что с ней делают, как стукач на допросе. А потом ты можешь прийти и спросить: «Ну что, вызывали тебя? А с какими аргументами?» И она тебе всё выложит, сука.

Как этого подопытного настроить

Перед тем как проверять, что он там наговорил, его обычно готовят:

  • mock.return_value: Говоришь ему: «Слушай, когда тебя вызовут, ты верни вот эту хуйню». И он возвращает.
  • mock.side_effect: А это уже поинтереснее. Тут можно устроить полный пиздец: заставить его кидать исключения, каждый раз возвращать разное или вообще выполнить какую-то функцию. Полный контроль, блядь.

А теперь лови его за язык (Assertions)

Вот основные вопросы, которые ты можешь ему задать после того, как он поработал:

  • mock.assert_called(): «Тебя вообще вызывали?» — «Да, вызывали, ёпта».
  • mock.assert_called_once(): «Тебя вызывали ровно один раз?» — «Один, блядь, больше не надо».
  • mock.assert_not_called(): «Тебя не трогали?» — «Нет, я чистый, как слеза младенца».
  • mock.assert_called_with(*args, **kwargs): «А в последний раз с какими аргументами тебя дернули?» — проверяет последний вызов.
  • mock.assert_called_once_with(*args, **kwargs): Комбо! «Тебя вызвали один раз и с такими-то параметрами?»
  • mock.assert_any_call(*args, **kwargs): «А вообще, среди всех твоих похождений, был вызов с вот этими штуками?»
  • mock.call_count: Просто посмотреть счётчик, блядь. Цифра. Сколько раз его ебали.

Ну и пример, чтобы не быть просто болтуном:

Допустим, у нас есть функция, которая шлёт уведомления через какой-то внешний API. Нам нужно проверить, что она это делает правильно, но реальный API не дергать — он же, блядь, может сдохнуть, или деньги счёт списать.

# Код, который мы тестируем
def send_notification(api_client, user_id, message):
    if len(message) > 0:
        api_client.send(user_id, text=message)

# Тест для этого кода
from unittest.mock import Mock
import unittest

class TestNotification(unittest.TestCase):
    def test_send_notification_with_message(self):
        # 1. Arrange: Лепим муляж API-клиента
        mock_api = Mock()

        # 2. Act: Запускаем нашу функцию, подсунув ей муляж
        send_notification(mock_api, 123, "Hello!")

        # 3. Assert: А теперь проверяем, не наврал ли муляж.
        # Говорим: «Метод .send() должен был вызваться один раз с вот этими параметрами».
        mock_api.send.assert_called_once_with(123, text="Hello!")

    def test_send_notification_with_empty_message(self):
        # 1. Arrange: Опять муляж
        mock_api = Mock()

        # 2. Act: Шлём пустое сообщение
        send_notification(mock_api, 123, "")

        # 3. Assert: А тут метод .send() вообще не должен был дергаться.
        # Если дернулся — значит, наша функция ебнулась.
        mock_api.send.assert_not_called()

if __name__ == '__main__':
    unittest.main(argv=['first-arg-is-ignored'], exit=False)

Вот и вся магия. Создал муляж, подсунул, проверил, что он сделал то, что нужно. Никаких лишних движений, всё чисто и под контролем. Красота, ёпта.