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. Наступного разу, коли ваш інтерфейс полетить у продакшен, ви будете знати: це не ми припустилися помилки, просто «зірки не зійшлися».

3
Опитування
Тестування FastAPI-додатків, рівень 5, лекція 4
Недоступний
Тестування FastAPI-додатків
Тестування FastAPI-додатків
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ