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

Что такое mock и когда его использовать?

2.3 Middle🔥 111 комментариев
#DevOps и инфраструктура#Django

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Mock: что это и когда использовать

Что такое Mock?

Mock — это объект-подделка, который заменяет реальный объект во время тестирования. Mock позволяет:

  • Изолировать тестируемый код
  • Избежать вызовов внешних сервисов (API, БД)
  • Контролировать поведение зависимостей
  • Проверить, как код взаимодействует с зависимостями

1. Основное использование Mock

from unittest.mock import Mock, patch, MagicMock
import requests

# Наш код, который тестируем
def fetch_user(user_id: int) -> dict:
    """Получить пользователя по ID из API"""
    response = requests.get(f"https://api.example.com/users/{user_id}")
    response.raise_for_status()
    return response.json()

# ТЕСТ БЕЗ MOCK (плохо)
def test_fetch_user_bad():
    # Проблемы:
    # 1. Делаем реальный HTTP запрос
    # 2. Зависит от интернета
    # 3. API может быть недоступна
    # 4. Тест медленный (~1-5 секунд)
    user = fetch_user(1)
    assert user['id'] == 1

# ТЕСТ С MOCK (хорошо)
def test_fetch_user_with_mock():
    # Создаем mock для requests.get
    with patch('requests.get') as mock_get:
        # Настраиваем mock
        mock_response = Mock()
        mock_response.json.return_value = {'id': 1, 'name': 'Alice'}
        mock_response.raise_for_status.return_value = None
        mock_get.return_value = mock_response
        
        # Вызываем функцию
        user = fetch_user(1)
        
        # Проверяем результат
        assert user == {'id': 1, 'name': 'Alice'}
        
        # Проверяем, что requests.get был вызван
        mock_get.assert_called_once_with('https://api.example.com/users/1')

2. Основные объекты Mock

from unittest.mock import Mock, MagicMock, patch

# Mock
mock_obj = Mock()
mock_obj.method.return_value = "result"
print(mock_obj.method())  # "result"

# Проверяем вызовы
print(mock_obj.method.call_count)  # 1
print(mock_obj.method.call_args)    # call()
print(mock_obj.method.called)       # True

# MagicMock (поддерживает magic методы)
magic = MagicMock()
magic[0] = "value"  # __setitem__
print(magic[0])     # "value"
print(len(magic))   # __len__ работает

# patch (заменяет объект в контексте)
with patch('module.function') as mock_func:
    mock_func.return_value = 42
    # Во время этого блока module.function заменена на mock

3. Практический пример: тестирование с БД

from unittest.mock import Mock, patch
from typing import Optional

# Код который тестируем
class UserRepository:
    def __init__(self, db_connection):
        self.db = db_connection
    
    def get_user(self, user_id: int) -> Optional[dict]:
        user = self.db.query(f"SELECT * FROM users WHERE id = {user_id}")
        return user
    
    def create_user(self, name: str, email: str) -> int:
        self.db.execute(
            "INSERT INTO users (name, email) VALUES (?, ?)",
            (name, email)
        )
        self.db.commit()
        return self.db.lastrowid()

# ТЕСТ
def test_get_user():
    # Создаем mock БД
    mock_db = Mock()
    mock_db.query.return_value = {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}
    
    # Создаем репозиторий с mock БД
    repo = UserRepository(mock_db)
    
    # Вызываем метод
    user = repo.get_user(1)
    
    # Проверяем результат
    assert user['name'] == 'Alice'
    mock_db.query.assert_called_once()

def test_create_user():
    mock_db = Mock()
    mock_db.lastrowid.return_value = 42
    
    repo = UserRepository(mock_db)
    user_id = repo.create_user("Bob", "bob@example.com")
    
    # Проверяем, что метод был вызван с правильными параметрами
    assert user_id == 42
    mock_db.execute.assert_called_once_with(
        "INSERT INTO users (name, email) VALUES (?, ?)",
        ("Bob", "bob@example.com")
    )
    mock_db.commit.assert_called_once()

4. Проверка вызовов Mock

from unittest.mock import Mock, call

mock_obj = Mock()

# Несколько вызовов
mock_obj.method(1)
mock_obj.method(2)
mock_obj.method(3)

# Проверки
print(mock_obj.method.call_count)  # 3

# assert_called_once() — вызван ровно один раз
mock2 = Mock()
mock2.func()
mock2.func.assert_called_once()

# assert_called_with() — вызван с конкретными аргументами
mock3 = Mock()
mock3.func(1, 2, 3)
mock3.func.assert_called_with(1, 2, 3)

# assert_has_calls() — проверить последовательность вызовов
mock4 = Mock()
mock4.func(1)
mock4.func(2)
mock4.func.assert_has_calls([call(1), call(2)])

# assert_not_called() — не вызван вообще
mock5 = Mock()
mock5.func.assert_not_called()

5. Side Effects (сложное поведение)

from unittest.mock import Mock

# Side effect может быть:

# 1. Список значений (возвращаемые по порядку)
mock = Mock()
mock.side_effect = [1, 2, 3]
print(mock())  # 1
print(mock())  # 2
print(mock())  # 3
# print(mock())  # StopIteration

# 2. Функция (вызывается вместо mock)
mock2 = Mock()
mock2.side_effect = lambda x: x * 2
print(mock2(5))  # 10
print(mock2(3))  # 6

# 3. Исключение (выбросить при вызове)
mock3 = Mock()
mock3.side_effect = ValueError("Error!")
try:
    mock3()
except ValueError as e:
    print(e)  # Error!

# 4. Комбинация (значения + исключение)
mock4 = Mock()
mock4.side_effect = [1, 2, ValueError("Done")]
print(mock4())  # 1
print(mock4())  # 2
try:
    mock4()
except ValueError:
    print("Exception raised")

6. patch() декоратор

from unittest.mock import patch
import requests

# Способ 1: Context manager
def test_with_context():
    with patch('requests.get') as mock_get:
        mock_get.return_value.json.return_value = {'status': 'ok'}
        # requests.get заменена на mock внутри блока
        response = requests.get('http://example.com')
        assert response.json() == {'status': 'ok'}

# Способ 2: Декоратор функции
@patch('requests.get')
def test_with_decorator(mock_get):
    mock_get.return_value.json.return_value = {'status': 'ok'}
    response = requests.get('http://example.com')
    assert response.json() == {'status': 'ok'}

# Способ 3: Несколько patch'ей
@patch('requests.post')
@patch('requests.get')
def test_multiple(mock_get, mock_post):
    mock_get.return_value.json.return_value = {'id': 1}
    mock_post.return_value.json.return_value = {'success': True}
    
    resp1 = requests.get('http://example.com')
    resp2 = requests.post('http://example.com', json={})

7. Тестирование обработки ошибок

from unittest.mock import Mock, patch
import requests

def fetch_data(url: str) -> dict:
    try:
        response = requests.get(url)
        response.raise_for_status()
        return response.json()
    except requests.ConnectionError:
        return {'error': 'Connection failed'}
    except requests.HTTPError:
        return {'error': 'HTTP error'}

def test_connection_error():
    with patch('requests.get') as mock_get:
        # Настраиваем mock выбросить исключение
        mock_get.side_effect = requests.ConnectionError("Network error")
        
        result = fetch_data('http://example.com')
        assert result == {'error': 'Connection failed'}

def test_http_error():
    with patch('requests.get') as mock_get:
        mock_response = Mock()
        mock_response.raise_for_status.side_effect = requests.HTTPError("404 Not Found")
        mock_get.return_value = mock_response
        
        result = fetch_data('http://example.com')
        assert result == {'error': 'HTTP error'}

Когда использовать Mock

СитуацияИспользуй Mock
Внешний API✅ Да (медленно, может быть недоступна)
База данных✅ Да (медленно, нужна test БД)
Файловая система✅ Да (медленно, оставляет файлы)
Текущее время✅ Да (недетерминировано)
Случайные числа✅ Да (непредсказуемо)
Простые функции❌ Нет (тестируй реально)
Внутренняя логика❌ Нет (интеграционные тесты)
Сетевые запросы✅ Да (VCR.py альтернатива)

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

# ❌ ПЛОХО: Мокируешь слишком много
with patch('module.func1'), patch('module.func2'), patch('module.func3'):
    # Сложно понять, что тестируешь
    pass

# ✅ ХОРОШО: Мокируешь только внешние зависимости
with patch('external_api.get_user') as mock_api:
    result = my_function()  # Мокируется только API
    assert result

# ✅ ХОРОШО: Используй assert_called_with для проверки
mock.func.assert_called_with(expected_arg)

# ✅ ХОРОШО: Очищай mock после теста (обычно pytest делает автоматически)
Что такое mock и когда его использовать? | PrepBro