Зачем мокать (mock) запросы в тестировании?

«Зачем мокать (mock) запросы в тестировании?» — вопрос из категории API тестирование, который задают на 24% собеседований AQA / Automation. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Мокирование (подмена) запросов — это ключевая техника для создания изолированных, быстрых и стабильных тестов. Основные причины её использования:

  • Изоляция тестируемого модуля: Позволяет тестировать логику клиентского кода или сервиса, не завися от работы реального внешнего API, базы данных или другого микросервиса. Если тот «упал» или изменился, мои тесты всё равно будут проходить.
  • Тестирование сложных и редких сценариев: Я могу легко смоделировать ответы, которые сложно или невозможно получить от реального сервиса в тестовом окружении.
    • Ошибки сервера: 500 Internal Server Error, 503 Service Unavailable.
    • Нестандартные ответы: Очень большие данные, специфичные форматы, медленные ответы (таймауты).
    • Edge-кейсы: Ответ с пустым телом, невалидный JSON.
  • Ускорение выполнения тестов: Моки работают в памяти, что в сотни раз быстрее, чем реальные сетевые вызовы.
  • Предотвращение side-эффектов: Тест не создаст запись в реальной продакшн-БД или не отправит настоящее SMS-сообщение.

Практический пример (Python, pytest с pytest-mock): Допустим, у меня есть функция get_user_data(user_id), которая внутри делает HTTP-запрос. Я хочу протестировать её логику обработки ответа.

import pytest
import requests

# Функция, которую тестируем
def get_user_data(user_id):
    response = requests.get(f"https://api.example.com/users/{user_id}")
    if response.status_code == 200:
        return response.json()["data"]
    elif response.status_code == 404:
        return None
    else:
        raise Exception("API Error")

# Сам тест с моком
def test_get_user_data_success(mocker):  # 'mocker' — фикстура из pytest-mock
    # 1. Создаём мок-ответ, который имитирует успешный API-ответ
    mock_response = mocker.Mock()
    mock_response.status_code = 200
    mock_response.json.return_value = {"data": {"id": 123, "name": "Alice"}}

    # 2. Подменяем реальный `requests.get` на наш мок
    # Теперь при вызове requests.get() в функции вернётся mock_response
    mocker.patch("requests.get", return_value=mock_response)

    # 3. Вызываем тестируемую функцию
    result = get_user_data(123)

    # 4. Проверяем, что функция корректно обработала мок-ответ
    assert result == {"id": 123, "name": "Alice"}
    # Дополнительно можно проверить, что запрос был вызван с правильным URL
    # requests.get.assert_called_once_with("https://api.example.com/users/123")

def test_get_user_data_not_found(mocker):
    mock_response = mocker.Mock()
    mock_response.status_code = 404
    mocker.patch("requests.get", return_value=mock_response)

    result = get_user_data(999)
    assert result is None  # Проверяем обработку 404 ошибки

Важно помнить, что моки должны максимально точно имитировать поведение реального сервиса, и их стоит использовать для unit- и component-тестов. Для интеграционного и end-to-end тестирования предпочтительнее использовать реальные сервисы или их точные стабы (stubs).