Короткий ответ
Нужно замокать клиент внешнего сервиса так, чтобы он бросал исключение таймаута (или возвращал ответ с кодом 500), и проверить, что код приложения перехватывает это и ведёт себя предсказуемо — например, возвращает понятную ошибку клиенту или делает повторную попытку по заданной политике.
Как это работает подробнее
В зависимости от HTTP-клиента симулируют конкретное исключение библиотеки (например, httpx.TimeoutException, requests.exceptions.Timeout) через side_effect мока, либо настраивают мок-сервер (responses/respx) так, чтобы он отвечал статусом 500 или зависал дольше настроенного таймаута, точно воспроизводя условия частичного отказа внешней зависимости.
Пример кода
testing_example_314.py
import httpx
import pytest
from unittest.mock import patch
def fetch_price_safely(client):
try:
response = client.get("/price")
response.raise_for_status()
return response.json()["price"]
except (httpx.TimeoutException, httpx.HTTPStatusError):
return None
def test_fetch_price_returns_none_on_timeout():
client = httpx.Client()
with patch.object(client, "get", side_effect=httpx.TimeoutException("timeout")):
assert fetch_price_safely(client) is NoneЧто использовать на практике
- Практика. Важно проверять не просто факт перехвата исключения, а итоговое поведение системы: вернулся ли клиенту корректный код ошибки (502/503), сработал ли circuit breaker или retry с backoff, залогировано ли событие для последующей диагностики. Это особенно важно для сервисов с частичными отказами внешних зависимостей.
Подводные камни
- Риск. Тестировать только happy path и ни разу не проверять поведение при ошибке внешнего сервиса — в production такие сбои случаются регулярно. Мокать ошибку способом, не соответствующим реальному поведению библиотеки (например, бросать общий Exception вместо специфичного TimeoutException), из-за чего тест не отражает реальный сценарий и обработчик ошибок в коде может его даже не перехватывать.