JavaRush /Курсы /Модуль 4: FastAPI /Мокирование внешних API в тестах

Мокирование внешних API в тестах

Модуль 4: FastAPI
5 уровень , 4 лекция
Открыта

Представьте, что ваш сервис обращается к внешнему API, например, чтобы получить информацию о погоде или текущий курс валют. Казалось бы, несложная задача. Однако реальность далека от идеала: серверы могут быть недоступны, запросы могут занимать много времени, а иногда вам просто не хочется тратить реальные деньги на API-лимиты. Тут-то и приходит на помощь мокирование.

Мокирование (mocking) — это процесс замены реальных зависимостей (например, внешнего API) поддельными объектами во время тестов. Это позволяет нам:

  • Тестировать без зависимости от внешнего сервиса. Даже если сервис упал, тесты все равно пройдут.
  • Ускорить выполнение тестов. Нет необходимости ждать ответа от сервера.
  • Контролировать поведение внешнего API. Мы можем заранее задать ответы и эмулировать ошибки.

Как мокировать внешние API в Pytest?

В Python есть несколько популярных инструментов для мокирования, но сегодня мы сосредоточимся на двух подходах:

  1. Использование встроенного unittest.mock — стандартный инструмент для создания моков.
  2. Использование библиотеки responses — специальный инструмент для мокирования HTTP-запросов.

Мокирование с помощью unittest.mock

Для начала давайте рассмотрим, как работает стандартный модуль unittest.mock. Он позволяет заменить реальные функции/объекты их поддельными версиями.

Пример: предположим, у нас есть метод, который обращается к API для получения данных о пользователе:


import httpx

async def get_user_data(user_id: int):
    url = f"https://api.example.com/users/{user_id}"
    async with httpx.AsyncClient() as client:
        response = await client.get(url)
    response.raise_for_status()
    return response.json()

Теперь мы хотим протестировать этот метод, но не отправлять реальные запросы. Используем unittest.mock:


from unittest.mock import AsyncMock, patch

@pytest.mark.asyncio
async def test_get_user_data():
    # Создаем фейковый ответ от API
    mock_response = {"id": 1, "name": "John Doe"}
    
    # Мокаем httpx.AsyncClient.get
    with patch("httpx.AsyncClient.get", new_callable=AsyncMock) as mock_get:
        mock_get.return_value.json.return_value = mock_response
        mock_get.return_value.raise_for_status = AsyncMock()

        # Вызываем нашу функцию
        result = await get_user_data(1)

        # Проверяем, что результат соответствует ожидаемому
        assert result == mock_response

        # Убедимся, что метод get вызывался с правильным URL
        mock_get.assert_called_once_with("https://api.example.com/users/1")

Здесь мы использовали patch, чтобы заменить реальную функцию httpx.AsyncClient.get поддельной версией, которая возвращает заранее заданный результат.

Мокирование с помощью библиотеки responses

Для мокирования HTTP-запросов библиотека responses предоставляет удобный интерфейс для эмуляции ответов на запросы.

Прежде всего, установим библиотеку:


pip install responses

Пример: представим тот же сценарий, но теперь с использованием responses.


import responses

@responses.activate
def test_get_user_data_responses():
    # Регистрируем фейковый ответ для конкретного URL
    responses.add(
        method=responses.GET,
        url="https://api.example.com/users/1",
        json={"id": 1, "name": "John Doe"},
        status=200
    )

    # Вызываем нашу функцию (обычная синхронная версия для примера)
    result = get_user_data_sync(1)  # предположим, есть синхронная версия

    # Проверяем результат
    assert result == {"id": 1, "name": "John Doe"}

Обратите внимание, что с responses мы можем легко управлять не только ответами, но и статусами HTTP-запросов. Например, можно протестировать обработку 404 ошибки:


@responses.activate
def test_get_user_data_not_found():
    responses.add(
        method=responses.GET,
        url="https://api.example.com/users/2",
        status=404
    )

    with pytest.raises(Exception):
        get_user_data_sync(2)

Сравнение подходов

Особенность unittest.mock responses
Гибкость Высокая Низкая
Простота использования для HTTP Ниже (нужно много кода) Намного проще
Встроенность Стандартная библиотека Python Требует установки
Управление ответами API Требует настройки вручную Интуитивно через методы add

Если вы работаете исключительно с HTTP-запросами, responses может быть проще и удобнее. Если же нужно мокировать любые функции/методы, выбирайте unittest.mock.


Практика: тестирование внешнего API

Теперь давайте реализуем простой сервис, который получает данные о пользователях из внешнего API и обрабатывает их.

Приведём пример FastAPI-приложения:


from fastapi import FastAPI, HTTPException
import httpx

app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    url = f"https://api.example.com/users/{user_id}"
    async with httpx.AsyncClient() as client:
        response = await client.get(url)

    if response.status_code == 404:
        raise HTTPException(status_code=404, detail="User not found")

    return response.json()

Тестирование с использованием responses


from fastapi.testclient import TestClient
import responses

client = TestClient(app)

@responses.activate
def test_get_user_success():
    # Мокаем успешный ответ
    responses.add(
        method=responses.GET,
        url="https://api.example.com/users/1",
        json={"id": 1, "name": "John Doe"},
        status=200
    )

    # Отправляем тестовый запрос
    response = client.get("/users/1")
    assert response.status_code == 200
    assert response.json() == {"id": 1, "name": "John Doe"}

@responses.activate
def test_get_user_not_found():
    # Мокаем 404 ошибку
    responses.add(
        method=responses.GET,
        url="https://api.example.com/users/2",
        status=404
    )

    # Отправляем тестовый запрос
    response = client.get("/users/2")
    assert response.status_code == 404
    assert response.json() == {"detail": "User not found"}

С помощью responses мы легко протестировали как успешный случай, так и обработку ошибки 404.


Типичные ошибки при мокировании

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

Еще одна ошибка — чрезмерное использование моков. Помните, что мокирование полезно, но оно не заменит интеграционное тестирование с реальными сервисами.


Выводы

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

Теперь, когда вы знаете основы и инструменты, вы готовы к созданию тестов для любых внешних API. В следующий раз, когда ваш интерфейс полетит в продакшен, вы будете знать: это не мы допустили ошибку, это просто "звёзды не сошлись".

1
Задача
Модуль 4: FastAPI, 5 уровень, 4 лекция
Недоступна
Использование `unittest.mock` для мокирования
Использование `unittest.mock` для мокирования
1
Задача
Модуль 4: FastAPI, 5 уровень, 4 лекция
Недоступна
Использование `responses` для обработки ошибок
Использование `responses` для обработки ошибок
3
Опрос
Тестирование FastAPI-приложений, 5 уровень, 4 лекция
Недоступен
Тестирование FastAPI-приложений
Тестирование FastAPI-приложений
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ