← Назад к вопросам
Как проверить, что код правильно взаимодействует с внешним сервисом?
2.0 Middle🔥 251 комментариев
#REST API и HTTP#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Проверка взаимодействия с внешним сервисом
Для тестирования кода, который взаимодействует с внешним API или сервисом, используются моки и VCR.py.
1. Mock/Patch
from unittest.mock import patch, MagicMock
import requests
def get_user(user_id):
response = requests.get(f'https://api.example.com/users/{user_id}')
return response.json()
# Тест
@patch('requests.get')
def test_get_user(mock_get):
# Настроить мок
mock_get.return_value.json.return_value = {'id': 1, 'name': 'John'}
# Вызов
result = get_user(1)
# Проверка
assert result == {'id': 1, 'name': 'John'}
mock_get.assert_called_once_with('https://api.example.com/users/1')
2. VCR.py (запись HTTP ответов)
pip install vcrpy
import vcr
import requests
@vcr.use_cassette('tests/cassettes/get_user.yaml')
def test_get_user():
# Первый запуск: записывает реальный HTTP ответ
# Последующие запуски: воспроизводят из файла
response = requests.get('https://api.example.com/users/1')
assert response.json()['name'] == 'John'
Файл cassette выглядит так:
interactions:
- request:
body: null
headers: {}
method: GET
uri: https://api.example.com/users/1
response:
status: {code: 200, message: OK}
headers: {}
body: {string: '{"id": 1, "name": "John"}'}
version: 1
3. Responses библиотека
pip install responses
import responses
import requests
@responses.activate
def test_api():
# Мокировать GET запрос
responses.add(
responses.GET,
'https://api.example.com/users/1',
json={'id': 1, 'name': 'John'},
status=200
)
# Выполнить тест
response = requests.get('https://api.example.com/users/1')
assert response.json()['name'] == 'John'
4. Httpretty (более гибкий)
import httpretty
import requests
@httpretty.activate
def test_api():
httpretty.register_uri(
httpretty.GET,
'https://api.example.com/users/1',
body='{"id": 1}',
content_type='application/json'
)
response = requests.get('https://api.example.com/users/1')
assert response.json()['id'] == 1
5. Проверка вызовов
from unittest.mock import call
@patch('requests.get')
def test_multiple_calls(mock_get):
mock_get.return_value.json.return_value = {'id': 1}
# Выполнить несколько вызовов
get_user(1)
get_user(2)
# Проверить вызовы
assert mock_get.call_count == 2
# Или проверить в деталях
mock_get.assert_has_calls([
call('https://api.example.com/users/1'),
call('https://api.example.com/users/2')
])
6. Асинхронные API (httpx)
import pytest
from unittest.mock import AsyncMock, patch
@pytest.mark.asyncio
@patch('httpx.AsyncClient.get')
async def test_async_api(mock_get):
# Настроить асинхронный мок
mock_response = AsyncMock()
mock_response.json = AsyncMock(return_value={'id': 1})
mock_get.return_value = mock_response
# Тест
client = httpx.AsyncClient()
response = await client.get('https://api.example.com/users/1')
data = await response.json()
assert data['id'] == 1
7. Реальный пример
# service.py
import requests
class UserService:
def __init__(self, base_url='https://api.example.com'):
self.base_url = base_url
def get_user(self, user_id):
url = f'{self.base_url}/users/{user_id}'
response = requests.get(url)
response.raise_for_status()
return response.json()
# test_service.py
from unittest.mock import patch
import pytest
@patch('requests.get')
def test_get_user(mock_get):
# Настроить мок
mock_response = mock_get.return_value
mock_response.json.return_value = {'id': 1, 'name': 'John'}
mock_response.raise_for_status = lambda: None
# Создать сервис
service = UserService()
# Вызвать метод
user = service.get_user(1)
# Проверить результат
assert user['id'] == 1
assert user['name'] == 'John'
# Проверить, как был вызван API
mock_get.assert_called_once_with('https://api.example.com/users/1')
8. Тестирование ошибок
from unittest.mock import patch
import requests
@patch('requests.get')
def test_api_error(mock_get):
# Настроить мок на ошибку
mock_get.side_effect = requests.ConnectionError("Network error")
# Проверить, что функция обрабатывает ошибку
with pytest.raises(requests.ConnectionError):
get_user(1)
9. Временное хранилище (Cassette)
import vcr
my_vcr = vcr.VCR(
cassette_library_dir='tests/cassettes',
record_mode='once', # Записать один раз, потом использовать
)
@my_vcr.use_cassette('test.yaml')
def test_api():
pass
Лучшие практики:
- Используй VCR для записи реальных ответов
- Мокируй внешние сервисы в unit тестах
- Проверяй параметры вызовов
- Тестируй обработку ошибок
- Используй разные fixtures для разных сценариев