Mock-объекты и фикстуры в JavaScript: как не сойти с ума на тестах

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Mock-объекты и фикстуры в JavaScript: ваш тестовый арсенал

Представьте, что вы пишете модуль, который отправляет запросы к внешнему API погоды. Вы запускаете тесты — и они падают, потому что API временно недоступен. Или хуже: вы тестируете платежную систему, и каждый прогон списывает реальные деньги с карты. Знакомо? Добро пожаловать в мир, где mock-объекты и фикстуры в JavaScript становятся вашими лучшими друзьями.

В этой статье мы не просто перечислим определения. Мы разберем, как с помощью моков, стабов и фикстур сделать тесты быстрыми, надежными и независимыми от внешнего мира. Вы узнаете, как обмануть таймеры, подменить HTTP-запросы и подготовить идеальные тестовые данные. Поехали!

Зачем нужны mock и фикстуры? Боль и спасение

Когда вы пишете юнит-тесты на JavaScript, вы хотите проверить только один модуль. Но реальный код часто зависит от баз данных, файловой системы, сетевых вызовов или случайных чисел. Без моков каждый тест — это лотерея: он может упасть из-за тайм-аута сети или из-за того, что сервер вернул не те данные.

Mock-объекты — это заменители реальных зависимостей. Они имитируют поведение оригинальных объектов, но вы полностью контролируете их ответы. Фикстуры — это заранее подготовленные наборы данных (например, JSON с ответом API), которые вы подставляете в тесты. Вместе они дают суперсилу: вы тестируете логику, а не инфраструктуру.

Mock vs Stub vs Spy: разбираемся в терминах

Перед тем как писать код, давайте наведем порядок в терминах. В экосистеме JavaScript (особенно в Jest и Mocha) эти понятия иногда смешивают, но различия важны:

  • Mock (мок) — объект, который заменяет реальную зависимость и позволяет задать ожидаемое поведение. Вы говорите: «Когда вызовут метод getUser с id=5, верни объект {name: 'Alice'}».
  • Stub (стаб) — упрощенная версия мока. Он просто возвращает фиксированные данные, не проверяя, как его вызвали. Часто используется для замены функций, которые возвращают случайные значения.
  • Spy (шпион) — обёртка над реальной функцией. Он не меняет её поведение, но записывает, сколько раз её вызвали, с какими аргументами и что она вернула. Идеально для проверки побочных эффектов.

В большинстве современных фреймворков (Jest, Sinon.js) моки умеют всё: и подменять реализацию, и шпионить. Но понимание разницы поможет писать более осмысленные тесты.

Фикстуры: как подготовить тестовые данные

Фикстуры в тестах JavaScript — это просто данные, которые вы выносите в отдельные файлы или переменные. Зачем это нужно? Во-первых, чтобы не дублировать 50-строчные JSON-объекты в каждом тесте. Во-вторых, чтобы быстро менять сценарии: одна фикстура для успешного ответа, другая — для ошибки.

Пример простой фикстуры для тестирования модуля работы с пользователями:

// fixtures/users.js
export const userAlice = {
  id: 1,
  name: 'Alice',
  email: 'alice@example.com',
  role: 'admin'
};

export const userBob = {
  id: 2,
  name: 'Bob',
  email: 'bob@example.com',
  role: 'user'
};

export const apiResponseSuccess = {
  status: 200,
  data: { users: [userAlice, userBob] }
};

export const apiResponseError = {
  status: 500,
  message: 'Internal Server Error'
};

Теперь в тестах мы просто импортируем эти объекты. Это делает код чище и позволяет легко воспроизводить кейсы: «А что, если сервер вернул 500?».

Mock в Jest: практические примеры

Jest — самый популярный тестовый фреймворк для JavaScript. Он встроен в Create React App, Next.js и многие другие инструменты. Рассмотрим, как создавать mock-объекты в Jest.

Mock функции с jest.fn()

Допустим, у нас есть функция, которая вызывает колбэк после обработки данных:

// utils/processor.js
export function processData(data, callback) {
  const result = data.map(item => item * 2);
  callback(result);
  return result;
}

В тесте мы хотим проверить, что колбэк вызван с правильными аргументами:

// __tests__/processor.test.js
import { processData } from '../utils/processor';

test('processData вызывает колбэк с удвоенными значениями', () => {
  const mockCallback = jest.fn();
  const input = [1, 2, 3];
  
  processData(input, mockCallback);
  
  expect(mockCallback).toHaveBeenCalledTimes(1);
  expect(mockCallback).toHaveBeenCalledWith([2, 4, 6]);
});

jest.fn() создает пустую функцию-заглушку. Мы не знаем, что она делает — нас интересует только факт вызова и аргументы. Это классический spy.

Mock модулей с jest.mock()

А теперь самое мощное. Допустим, ваш модуль импортирует axios и делает запросы к API. Вы не хотите, чтобы тесты реально ходили в сеть. Используем jest.mock():

// services/userService.js
import axios from 'axios';

export async function getUser(id) {
  const response = await axios.get(`/api/users/${id}`);
  return response.data;
}
// __tests__/userService.test.js
import { getUser } from '../services/userService';
import axios from 'axios';

jest.mock('axios'); // Автоматически подменяет весь модуль axios

test('getUser возвращает данные пользователя', async () => {
  const mockUser = { id: 1, name: 'Alice' };
  axios.get.mockResolvedValue({ data: mockUser });
  
  const result = await getUser(1);
  
  expect(axios.get).toHaveBeenCalledWith('/api/users/1');
  expect(result).toEqual(mockUser);
});

Обратите внимание: мы не писали никакой реальной реализации. Jest подменил axios так, что все его методы (get, post, etc.) стали моками. Мы просто сказали: «Когда вызовут get, верни промис, который резолвится в {data: mockUser}». Тест пройдёт за миллисекунду и не зависит от сети.

Mock таймеров с jest.useFakeTimers()

Функции, которые используют setTimeout или setInterval, — ещё одна головная боль. Как протестировать код, который ждёт 5 секунд? Ответ: с помощью фейковых таймеров.

// utils/delayedMessage.js
export function showDelayedMessage(message, delay, callback) {
  setTimeout(() => {
    callback(message);
  }, delay);
}
// __tests__/delayedMessage.test.js
import { showDelayedMessage } from '../utils/delayedMessage';

jest.useFakeTimers();

test('showDelayedMessage вызывает колбэк через заданное время', () => {                

Блоги

Рекомендаций по книгам