Перейти к содержанию
Innovialabs

Моки, стабы и фейки: как изолировать код в тестах

Практика3 мин

Коротко

Test double — подставной объект вместо реальной зависимости: stub возвращает заготовленные данные, mock проверяет вызовы, fake — упрощённая рабочая реализация. Подменяйте только медленное и внешнее.

Зачем подменять зависимости

Юнит-тест должен быть быстрым, стабильным и проверять одну вещь. Реальная сеть, база данных или платёжный шлюз мешают этому: они медленные, могут быть недоступны и возвращают разные данные. Поэтому в тестах их заменяют test doubles — «дублёрами», как в кино.

Пять видов дублёров

ТипЧто делаетКогда нужен
DummyПросто занимает место аргумента, не используетсяПараметр обязателен, но в тесте не важен
StubВозвращает заготовленный ответНужно задать входные данные от зависимости
SpyРаботает как stub и запоминает вызовыНужно потом посмотреть, как вызывали
MockЗаранее знает ожидаемые вызовы и проверяет ихВажен сам факт взаимодействия: письмо отправлено, платёж создан
FakeУпрощённая, но рабочая реализацияНужна логика: in-memory база, локальное хранилище файлов

В повседневной речи всё это часто называют «моками», а библиотеки вроде unittest.mock и Jest создают объекты, которые могут играть любую из этих ролей. Важно понимать роль в конкретном тесте: вы задаёте данные (stub) или проверяете поведение (mock).

Подмена HTTP-запроса в pytest

Функция получает температуру из внешнего API:

# weather.py
import requests

def get_temp(city):
    r = requests.get("https://api.example.com/weather",
                     params={"q": city}, timeout=5)
    r.raise_for_status()
    return r.json()["temp"]

Тест подменяет requests.get там, где он используется — в модуле weather:

from unittest.mock import Mock, patch
from weather import get_temp

def test_get_temp_reads_temp_field():
    response = Mock()
    response.json.return_value = {"temp": 21}
    with patch("weather.requests.get", return_value=response) as get:
        assert get_temp("Tashkent") == 21
        get.assert_called_once()

Частая ошибка — патчить requests.get в другом месте, а не по пути, через который его импортирует тестируемый модуль.

Подмена fetch в Jest

// user.js
export async function getUserName(id) {
  const res = await fetch(`/api/users/${id}`);
  const data = await res.json();
  return data.name;
}
// user.test.js
import { getUserName } from './user';

test('returns user name', async () => {
  global.fetch = jest.fn().mockResolvedValue({
    json: async () => ({ name: 'Aziz' }),
  });
  await expect(getUserName(1)).resolves.toBe('Aziz');
  expect(fetch).toHaveBeenCalledWith('/api/users/1');
});

Для целых модулей в Jest есть jest.mock('./db'): все экспорты становятся управляемыми функциями-заглушками.

База данных: fake вместо мока

Мокать каждый SQL-запрос — путь к хрупким тестам. Удобнее спрятать базу за интерфейсом репозитория и подставить в тестах fake:

class FakeUserRepo:
    def __init__(self):
        self.users = {}

    def save(self, user):
        self.users[user["email"]] = user

    def find_by_email(self, email):
        return self.users.get(email)

Логика регистрации тестируется на FakeUserRepo, а работа с настоящей базой проверяется отдельными интеграционными тестами — например, с тестовой БД в Docker.

Опасность избыточного мокирования

Признаки, что моков слишком много:

  • тест повторяет реализацию строка за строкой: «вызвать A, потом B с такими аргументами»;
  • любой рефакторинг без изменения поведения ломает тесты;
  • все тесты зелёные, а в продакшене ошибка, потому что мок вёл себя не как реальный сервис;
  • для одного теста настраивается пять и больше подмен.

Как избежать:

  • Подменяйте только границы системы: сеть, БД, время, файловую систему, сторонние SDK.
  • Свою чистую логику не мокайте — вызывайте её по-настоящему.
  • Проверяйте результат, а не последовательность внутренних вызовов, если взаимодействие само по себе не является требованием.
  • Держите рядом интеграционные тесты, которые проверяют реальную связку.

Частые вопросы

Stub только отдаёт данные, и тест проверяет результат функции. Mock участвует в проверке: тест утверждает, что его вызвали нужное число раз с нужными аргументами.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.