← Назад к вопросам

Как проверить, что код правильно взаимодействует с внешним сервисом?

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

Лучшие практики:

  1. Используй VCR для записи реальных ответов
  2. Мокируй внешние сервисы в unit тестах
  3. Проверяй параметры вызовов
  4. Тестируй обработку ошибок
  5. Используй разные fixtures для разных сценариев
Как проверить, что код правильно взаимодействует с внешним сервисом? | PrepBro