Что такое моки (mocks) в тестировании и когда их следует применять?

Ответ

Моки (Mock objects) — это объекты-заглушки, которые имитируют поведение реальных зависимостей (например, базы данных, внешних API, файловой системы) в unit-тестах. Они позволяют изолировать тестируемый код, проверять взаимодействие с зависимостями и инжектировать тестовые данные.

Ключевая цель: Проверить, что тестируемый модуль правильно вызывает методы своих зависимостей с ожидаемыми аргументами.

Пример на Python с библиотекой unittest.mock:

from unittest.mock import Mock, patch
import requests

def fetch_user_data(user_id):
    # Эта функция зависит от внешнего API
    response = requests.get(f"https://api.example.com/users/{user_id}")
    return response.json() if response.status_code == 200 else None

# Тест с моком для requests.get
@patch('requests.get')
def test_fetch_user_data_success(mock_get):
    # 1. Настраиваем мок: задаём возвращаемое значение
    mock_response = Mock()
    mock_response.status_code = 200
    mock_response.json.return_value = {"id": 1, "name": "John"}
    mock_get.return_value = mock_response

    # 2. Выполняем тестируемую функцию
    result = fetch_user_data(1)

    # 3. Проверяем РЕЗУЛЬТАТ работы функции
    assert result == {"id": 1, "name": "John"}

    # 4. Проверяем ВЗАИМОДЕЙСТВИЕ: функция вызвала API с правильным URL
    mock_get.assert_called_once_with("https://api.example.com/users/1")

Когда использовать моки:

  • Unit-тестирование сервисов, которые зависят от медленных или нестабильных внешних систем (платёжные шлюзы, почтовые серверы).
  • Тестирование исключительных сценариев, которые сложно воспроизвести в реальной системе (таймауты, ошибки 500).
  • Верификация бизнес-логики, которая заключается в вызове определённых методов с конкретными параметрами.

Альтернативы и когда их выбирать:

  • Стабы (Stubs) — только подменяют ответ, не проверяют взаимодействие. Используются, когда важен только результат.
  • Фейки (Fakes) — упрощённая, но рабочая реализация зависимости (например, in-memory база данных). Подходят для интеграционных тестов.
  • Реальные зависимости — используются в end-to-end (E2E) тестах для проверки всей системы в сборе.

Использование моков делает тесты быстрыми, стабильными и сфокусированными на конкретной единице кода.

Ответ 18+ 🔞

А, моки, блядь! Ну это ж классика, ёпта! Представь себе: сидишь ты пишешь код, а он, сука, тянет за собой кучу зависимостей — база данных, какой-нибудь внешний апишник, который отвечает раз в полгода, файловая система... И как его, блядь, протестировать-то? Ждать, пока этот левый сервис очухается? Да ну нахуй!

Вот тут-то и выходят на сцену моки (Mock objects), ебать мои старые костыли! Это такие подставные ублюдки, которые притворяются твоими настоящими зависимостями. Их задача — позволить тебе проверить твой код в изоляции, как будто он один в пустой комнате, и посмотреть, как он общается с этими самыми зависимостями. То есть, ты не просто смотришь, что функция вернула, а ты смотришь — а вызвала ли она вообще тот самый метод у соседа, и с теми ли аргументами, с которыми надо?

Смотри, как это выглядит на Python с unittest.mock:

from unittest.mock import Mock, patch
import requests

def fetch_user_data(user_id):
    # Эта функция зависит от внешнего API
    response = requests.get(f"https://api.example.com/users/{user_id}")
    return response.json() if response.status_code == 200 else None

# Тест с моком для requests.get
@patch('requests.get')
def test_fetch_user_data_success(mock_get):
    # 1. Настраиваем мок: задаём возвращаемое значение
    mock_response = Mock()
    mock_response.status_code = 200
    mock_response.json.return_value = {"id": 1, "name": "John"}
    mock_get.return_value = mock_response

    # 2. Выполняем тестируемую функцию
    result = fetch_user_data(1)

    # 3. Проверяем РЕЗУЛЬТАТ работы функции
    assert result == {"id": 1, "name": "John"}

    # 4. Проверяем ВЗАИМОДЕЙСТВИЕ: функция вызвала API с правильным URL
    mock_get.assert_called_once_with("https://api.example.com/users/1")

Видишь, в чём прикол? Мы не ждём ответа от реального сервера, который может лежать, тормозить или требовать токен. Мы подменили целый requests.get на свою куклу! И теперь можем проверить две вещи: что функция вернула правильные данные (шаг 3) и что она, сука, вообще позвонила по правильному адресу (шаг 4). Вот это второй пункт — это и есть сила моков, их взаимодействие.

Так когда же эту банду подставных лиц запускать?

  • Юнит-тесты для сервисов, которые цепляются к медленным или нестабильным штукам (платежки, почта, смс-ки). Чтобы не ждать, пока они там свои дела сделают.
  • Тестирование всяких пиздецов, которые в жизни случаются раз в пятилетку: таймауты, ошибки 500, отказ сети. С моком ты можешь создать эту ошибку по щелчку.
  • Проверка бизнес-логики, которая по сути сводится к команде: "эй, сосед, вызови-ка свой метод save() с вот этими данными". Мок это подтвердит или опровергнет.

А что, есть ещё кто-то?

Ну конечно, блядь! Не одними моками сыты:

  • Стабы (Stubs) — это такие тихони. Они просто подменяют ответ, но им похуй, вызывали их или нет. Используешь, когда тебе важен только конечный результат, а не процесс.
  • Фейки (Fakes) — это уже более навороченные ребята. У них внутри есть какая-никакая, но логика. Типа база данных в оперативке. Уже ближе к интеграционке.
  • Настоящие зависимости — это для полного, блядь, погружения, в E2E-тестах. Когда надо проверить, что вся система от и до не развалится, как карточный домик.

Короче, моки — это твои лучшие друзья, когда надо сделать тесты быстрыми, стабильными и сфокусированными на одной конкретной детали, а не на всей вселенной вокруг. Без них — пиши пропало, будешь ждать ответа от внешнего апи, как дурак.