Mocking и stubbing: имитация функционала в тестах

Зачем нужны моки и стабы? 🤔

В реальных проектах код часто зависит от внешних сервисов: API баз данных, сторонних библиотек, сетевых запросов. Если запускать тесты с реальными вызовами:
- Тесты становятся медленными (например, ждём ответа от сервера).
- Результаты тестов нестабильны (API может упасть, данные измениться).
- Тестируем не только свою логику, но и чужой код.

Решение: подменить реальные зависимости на контролируемые заглушки — моки (mock) и стабы (stub).

💡 Фишка: Моки и стабы помогают изолировать тестируемый модуль, чтобы проверять только его поведение, а не работу внешних систем.


Стабы (Stubs) — простые заглушки 🧩

Стаб — это заглушка, которая возвращает предопределённые данные вместо реальных вызовов.

Пример: тестируем функцию, которая проверяет статус заказа через API:

// Реальная функция (зависит от API)
async function isOrderCompleted(orderId) {
  const response = await fetch(`/api/orders/${orderId}`);
  const data = await response.json();
  return data.status === 'completed';
}

Тест с стабом:

// Подменяем `fetch` на стаб
const fakeApiResponse = { status: 'completed' };
global.fetch = () => Promise.resolve({ json: () => fakeApiResponse });

test('Order is completed', async () => {
  const result = await isOrderCompleted(123);
  expect(result).toBe(true); // ✅ Тест не зависит от реального API!
});

Когда использовать стабы?

✔ Нужно просто вернуть данные без проверки логики.
✔ Тестируем реакцию кода на разные ответы (успех/ошибка).


Моки (Mocks) — умные заглушки с проверками 🕵️

Мок — это стаб с дополнительной логикой, который запоминает, как его вызывали, и позволяет проверять:
- Сколько раз вызывали функцию?
- С какими аргументами?
- Была ли вызвана вообще?

Пример: тестируем отправку уведомления:

function sendNotification(user, message) {
  if (!user.isAdmin) {
    console.error('Access denied');
    return;
  }
  console.log(`Message sent: ${message}`);
}

Тест с моком console.log:

beforeEach(() => {
  // Подменяем console.log на мок
  console.log = jest.fn();
});

test('Admin can send notifications', () => {
  const adminUser = { isAdmin: true };
  sendNotification(adminUser, 'Hello!');

  // Проверяем, что console.log вызван с правильным сообщением
  expect(console.log).toHaveBeenCalledWith('Message sent: Hello!');
});

test('Non-admin cannot send notifications', () => {
  const regularUser = { isAdmin: false };
  sendNotification(regularUser, 'Hello!');

  // Проверяем, что console.log не вызывался
  expect(console.log).not.toHaveBeenCalled();
});

Когда использовать моки?

✔ Нужно проверить взаимодействие между модулями.
✔ Важно убедиться, что функция вызвана правильно.


Популярные библиотеки для мокинга 🛠

  1. Jest — встроенные моки (jest.fn(), jest.spyOn()).
  2. Sinon.js — мощные стабы, моки, фейковые серверы.
  3. Nock — мокирование HTTP-запросов.

Пример с jest.spyOn:

const api = {
  fetchData: () => 'real data'
};

test('Mocking a method', () => {
  // Подменяем метод на мок
  jest.spyOn(api, 'fetchData').mockReturnValue('fake data');

  expect(api.fetchData()).toBe('fake data'); // ✅
});

Итоги: моки vs стабы 🏁

Характеристика Стаб (Stub) Мок (Mock)
Цель Возвращает данные Проверяет вызовы
Проверка Реакция на данные Как вызван? Сколько раз?
Сложность Простые значения Контроль поведения
Пример getUser(){id: 1} sendEmail() → проверка, что вызван 1 раз

🚀 Совет от Данилы Бежина: «Моки — это не только заглушки, но и мощный инструмент для проверки архитектуры кода. Если приходится слишком часто их использовать, возможно, стоит пересмотреть зависимости между модулями».

Теперь вы готовы тестировать код, не завися от внешнего мира! Попробуйте Jest или Sinon.js — и пишите стабильные тесты. 💪

Скрыть рекламу навсегда

🌱 Индвидидулаьные занятия

Индивидуальные онлайн-занятия по программированию для детей и подростков

Личный подход, без воды, с фокусом на понимание и реальные проекты.

🚀 Записаться на занятие