---
title: "Чрезмерное использование моков"
type: "test-smell"
slug: "excessive-mocking"
url: "http://localhost:3000/ru/test-smells/excessive-mocking.md"
category: "Запахи мокирования"
description: "Тест настраивает столько мок-объектов и заглушенных взаимодействий, что настройка моков затмевает саму проверку, поэтому тест в итоге испытывает моки, а не реальное поведение."
---
# Чрезмерное использование моков

> Тест настраивает столько мок-объектов и заглушенных взаимодействий, что настройка моков затмевает саму проверку, поэтому тест в итоге испытывает моки, а не реальное поведение.

## Signs and Symptoms

Тест — это в основном _подготовка_: длинные блоки создания моков, строки `when(...).thenReturn(...)` / `mockReturnValue(...)` и `verify(...)`, при малом количестве реального проверяемого кода. Выдающие признаки:

* **Обвязки моков больше, чем проверок.** Настройка — много строк; «действие» — один вызов; «проверка» убеждается, что моки _вызвали_, а не что результат верен.
* **Мокание того, чем владеете вы.** Доменные объекты, объекты-значения или сотрудники с чистой логикой мокаются вместо мокания лишь внешних границ (сеть, БД, часы, файловая система, сторонние сервисы).
* **Цепочки моков / «крушения поездов».** Мок возвращает другой мок, который возвращает ещё один мок, зеркаля граф вызовов продакшена.
* **Проверка только взаимодействий.** Тест проверяет `toHaveBeenCalledWith(...)` у каждого сотрудника и ни разу не проверяет возвращённое значение или итоговое состояние.
* **Хрупкость при рефакторинге.** Перестановка внутренних вызовов или извлечение метода ломает множество тестов, хотя поведение не изменилось.
* **Пять и более моков лишь для создания экземпляра SUT** — обычно признак того, что у самой SUT слишком много зависимостей.

```js
test('places order', () => {
  const inventory = { check: jest.fn().mockReturnValue(true) };
  const pricing   = { quote: jest.fn().mockReturnValue(42) };
  const tax       = { calc:  jest.fn().mockReturnValue(4.2) };
  const wallet    = { charge: jest.fn().mockReturnValue({ ok: true }) };
  const ledger    = { record: jest.fn() };
  const emailer   = { send: jest.fn() };
  const audit     = { log: jest.fn() };
  const clock     = { now: jest.fn().mockReturnValue(0) };

  const svc = new OrderService(inventory, pricing, tax, wallet, ledger, emailer, audit, clock);
  svc.place(cart);

  // проверяет, что моки были вызваны — а не что заказ верен
  expect(inventory.check).toHaveBeenCalled();
  expect(pricing.quote).toHaveBeenCalled();
  expect(wallet.charge).toHaveBeenCalledWith(46.2);
  expect(ledger.record).toHaveBeenCalled();
});

```

## Reasons for the Problem

**Почему это происходит**

* **У проверяемой системы (SUT) слишком много сотрудников-зависимостей.** Чрезмерное использование моков обычно — сигнал о _дизайне_: класс с низкой связностью и множеством зависимостей вынуждает каждый тест поднимать их все. Как формулирует каталожная литература, _«если нужно замокать пять внутренних классов лишь для того, чтобы протестировать один метод, у метода слишком много зависимостей — это проблема дизайна, а не тестирования»._
* **Привычка / рефлекс «мокать всё».** Доведение тестирования на основе взаимодействий («лондонская школа») до крайности или рефлекторное мокание ради того, чтобы не трогать БД или сеть, ведёт к моканию чистой логики, которую можно было бы протестировать напрямую.
* **Трудно строимые реальные объекты.** Когда реальные сотрудники-зависимости неудобно конструировать, мок кажется проще, чем чинить конструктор или добавлять подделку (fake).

**Почему это вредно**

* **Ложная уверенность.** Моки кодируют _ваши допущения_ о зависимости. Если реальная реализация расходится с ними, тест всё равно проходит — например, заглушка, утверждающая, что `sum()` возвращает только положительные целые, сохраняет зелёный тест после изменения реального метода. Такие тесты могут стать тавтологичными: они лишь проверяют, что написанные вами моки ведут себя как написанные вами моки. Как предупреждает документация самого Mockito, _«если замокано всё, тестируем ли мы вообще продакшен-код?»_
* **Хрупкость / высокая стоимость сопровождения.** Проверка конкретных вызовов делает детали реализации частью контракта, поэтому безобидные рефакторинги (порядок вызовов, извлечённые помощники) ломают тесты. Это _Overspecified Software_ / _Fragile Test_ у Месароса.
* **Плохая читаемость.** Сотни строк конфигурации зависимостей погребают то единственное, о чём этот тест, во многом как _Mystery Guest_ — ревьюер не может понять, какое поведение на самом деле проверяется.
* **Пропущенные интеграционные баги.** Реальная связка между компонентами никогда не задействуется; баги живут именно в швах, которые заменили моки. Высокое покрытие маскирует низкое качество.

## Treatment

Относитесь к тяжёлому моканию как к обратной связи, а затем снижайте _нужду_ мокать:

1. **Сначала исправьте дизайн.** Если для сборки SUT приходится мокать 5+ сотрудников, разделите ответственности или сократите зависимости конструктора. Меньше реальных зависимостей — меньше моков.
2. **Мокайте только на архитектурных границах.** Мокайте то, чем вы _не владеете_ (сеть, БД, часы, файловая система, сторонние API); используйте **реальные экземпляры** собственных доменных объектов и объектов-значений.
3. **Извлеките чистое ядро (functional core / imperative shell).** Вынесите логику вычислений/решений из класса, насыщенного вводом-выводом, чтобы её можно было тестировать с **нулём моков**, оставив тонкую оболочку, которой нужны лишь один-два дублёра на границе.
4. **Предпочитайте проверку состояния проверке взаимодействий.** Проверяйте возвращённое значение или итоговое состояние вместо `verify(...)`/`toHaveBeenCalledWith(...)` у каждого сотрудника. Оставьте проверки взаимодействий для того единственного побочного эффекта, который по-настоящему важен.
5. **Используйте простейшего дублёра, который работает.** Замените сложные моки **заглушками** (просто возвращают значения) или единым переиспользуемым **in-memory fake** вместо переопределения каждого метода в каждом тесте.
6. **Выявляйте избыточное мокание динамически.** Включение строгих заглушек Mockito (по умолчанию `MockitoExtension`/`MockitoJUnitRunner`) выбрасывает `UnnecessaryStubbingException` для настроенных, но ни разу не использованных заглушек — дешёвый способ найти моки, которые вам не нужны.

```js
// ДО: 8 моков, проверяем вызовы
const pricing = { quote: jest.fn().mockReturnValue(42) };
const tax     = { calc:  jest.fn().mockReturnValue(4.2) };
// + ещё 6 моков ...
expect(wallet.charge).toHaveBeenCalledWith(46.2);

// ПОСЛЕ: чистая логика тестируется напрямую — без моков
expect(totalFor(cart, rates)).toBe(46.2);

// подделана лишь реальная граница; проверяем состояние, а не вызовы
const wallet = new InMemoryWallet({ balance: 100 });
const svc = new OrderService(new InMemoryInventory(cart), wallet);
const order = svc.place(cart);
expect(order.total).toBe(46.2);
expect(wallet.balance).toBe(53.8);

```
