Мокирование зависимостей

Мокирование зависимостей — это техника тестирования, при которой реальные зависимости заменяются управляемыми объектами-заглушками (mock objects). Эти объекты имитируют поведение настоящих компонентов, но позволяют полностью контролировать их ответы, состояние и побочные эффекты.

В контексте интерфейсных библиотек, таких как Muuri, мокирование применяется при тестировании:

  • взаимодействия сетки с внешними сервисами
  • интеграции с DOM-утилитами
  • работы пользовательских адаптеров
  • обработчиков событий
  • источников данных

Muuri активно взаимодействует с:

  • DOM-элементами
  • механизмами перетаскивания
  • системой событий
  • сторонними библиотеками анимации
  • пользовательскими функциями

Во время модульного тестирования реальные зависимости могут:

  • замедлять тесты
  • создавать нестабильность
  • вызывать побочные эффекты
  • усложнять воспроизводимость результатов

Мокирование решает эти проблемы.


Зависимости внутри Muuri

Muuri — библиотека динамических сеток, работающая поверх DOM и использующая внутреннюю систему управления элементами.

К ключевым зависимостям относятся:

1. DOM API

Используются:

  • document.createElement
  • getBoundingClientRect
  • appendChild
  • removeChild

2. Система событий

Muuri генерирует события:

  • layoutStart
  • layoutEnd
  • move
  • add
  • remove

3. Drag & Drop механизм

Перетаскивание зависит от:

  • указателя мыши
  • сенсорных событий
  • внутреннего движка позиционирования

4. Анимационная система

Muuri использует:

  • CSS transitions
  • requestAnimationFrame
  • собственные вычисления позиций

5. Пользовательские функции

Например:

  • dragStartPredicate
  • layout
  • sortData

Все эти зависимости могут быть замокированы.


Причины использования мокирования

Изоляция тестируемого кода

При тестировании логики работы сетки необходимо изолировать:

  • обработчики событий
  • вычисление сортировки
  • изменение состояния элементов

Мокирование позволяет отключить:

  • реальные анимации
  • DOM-измерения
  • физическое перемещение элементов

Повышение скорости тестирования

DOM-операции и анимации значительно замедляют тесты.

Мок-объекты позволяют:

  • мгновенно возвращать значения
  • пропускать сложные вычисления
  • отключать рендеринг

Управляемые сценарии

С помощью моков можно воспроизводить сложные ситуации:

  • ошибка загрузки данных
  • задержка ответа
  • некорректные размеры элементов
  • конфликт сортировки

Типы тестовых объектов

В тестировании применяются несколько типов заменителей.

Stub

Возвращает заранее определённые данные.

Пример:

const getBoundingClientRectStub = () => ({
  width: 200,
  height: 100,
  top: 0,
  left: 0
});

Mock

Фиксирует вызовы и позволяет проверять их.

const mockCallback = jest.fn();

Spy

Наблюдает за вызовами реального метода.

jest.spyOn(grid, 'layout');

Fake

Полноценная упрощённая реализация.

Например, поддельный источник данных.


Мокирование DOM для Muuri

Muuri активно использует DOM-измерения.

Наиболее критичный метод:

element.getBoundingClientRect()

В тестах его часто заменяют.

Пример мокирования

Element.prototype.getBoundingClientRect = jest.fn(() => ({
  width: 100,
  height: 100,
  top: 0,
  left: 0,
  right: 100,
  bottom: 100
}));

Теперь Muuri всегда будет считать элементы одинакового размера.

Это делает тесты:

  • детерминированными
  • стабильными
  • быстрыми

Мокирование requestAnimationFrame

Muuri использует requestAnimationFrame для анимации layout.

В тестовой среде его часто заменяют синхронной функцией.

global.requestAnimationFrame = (cb) => cb();

Это позволяет:

  • убрать задержки
  • выполнять layout мгновенно
  • проверять результаты сразу

Мокирование drag-логики

Drag-механизм Muuri сложен и зависит от событий браузера.

Во многих тестах перетаскивание отключают.

const grid = new Muuri(container, {
  dragEnabled: false
});

Если требуется протестировать логику drag-событий, можно замокировать предикат запуска.

const grid = new Muuri(container, {
  dragStartPredicate: () => true
});

Для более точного контроля используют mock-функции.

const dragPredicateMock = jest.fn(() => true);

Мокирование пользовательских layout-функций

Muuri позволяет переопределять алгоритм layout.

В тестах иногда требуется проверить:

  • вызывается ли layout
  • какие параметры передаются

Пример:

const layoutMock = jest.fn((grid, layoutId, items, width, height, callback) => {
  callback({
    id: layoutId,
    items: items
  });
});

Использование:

const grid = new Muuri(container, {
  layout: layoutMock
});

Проверка:

expect(layoutMock).toHaveBeenCalled();

Мокирование источников данных

Muuri часто используется вместе с API или локальными хранилищами.

Например, сетка наполняется элементами после загрузки данных.

Реальный запрос:

fetch('/api/items')

В тестах такой запрос заменяют.

Пример

global.fetch = jest.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve([
      { id: 1 },
      { id: 2 }
    ])
  })
);

Теперь тесты не зависят от сети.


Мокирование событий Muuri

Muuri генерирует события через собственную систему.

Пример:

grid.on('layoutEnd', handler);

Для тестирования можно использовать mock-обработчик.

const handler = jest.fn();

grid.on('layoutEnd', handler);
grid.layout();

expect(handler).toHaveBeenCalled();

Это позволяет проверять:

  • факт вызова
  • количество вызовов
  • переданные параметры

Мокирование методов экземпляра Muuri

Иногда необходимо заменить внутренние методы.

Например:

grid.layout()

С помощью spy можно отслеживать вызовы.

const spy = jest.spyOn(grid, 'layout');

grid.add(element);

expect(spy).toHaveBeenCalled();

Это проверяет, что добавление элемента инициирует перерасчёт сетки.


Мокирование сортировки

Muuri поддерживает пользовательскую сортировку через sortData.

Пример настройки:

const grid = new Muuri(container, {
  sortData: {
    price: (item, element) => {
      return parseFloat(element.dataset.price);
    }
  }
});

В тестах можно замокировать вычисление.

const priceMock = jest.fn(() => 10);

Использование:

const grid = new Muuri(container, {
  sortData: {
    price: priceMock
  }
});

Проверка:

expect(priceMock).toHaveBeenCalled();

Мокирование таймеров

Muuri использует:

  • setTimeout
  • requestAnimationFrame

Для тестов применяют fake timers.

Пример с Jest:

jest.useFakeTimers();

Теперь можно контролировать время.

jest.advanceTimersByTime(100);

Это позволяет тестировать:

  • отложенные layout
  • debounce-механизмы
  • анимации

Мокирование размеров контейнера

Muuri вычисляет размеры контейнера при layout.

В тестах контейнер может не иметь реальных размеров.

Используется мок:

Object.defineProperty(container, 'clientWidth', {
  value: 800
});

И:

Object.defineProperty(container, 'clientHeight', {
  value: 600
});

Это гарантирует корректные вычисления сетки.


Мокирование добавления элементов

Метод:

grid.add(element)

может инициировать множество внутренних процессов.

В тестах иногда создают фиктивные элементы.

function createMockItem() {
  const el = document.createElement('div');
  el.className = 'item';
  return el;
}

Использование:

const item = createMockItem();
grid.add(item);

Контроль побочных эффектов

Muuri изменяет:

  • позиционирование элементов
  • стили
  • DOM-структуру

Для тестов можно замокировать методы изменения стилей.

HTMLElement.prototype.style = {};

Или отслеживать изменения через spy.

const styleSpy = jest.spyOn(element.style, 'transform', 'set');

Мокирование удаления элементов

Удаление инициирует события:

grid.remove(item);

Проверка через mock:

const removeHandler = jest.fn();

grid.on('remove', removeHandler);

grid.remove(item);

expect(removeHandler).toHaveBeenCalled();

Мокирование фильтрации

Muuri поддерживает фильтрацию:

grid.filter('.active');

Можно замокировать функцию фильтра.

const filterMock = jest.fn(() => true);

grid.filter(filterMock);

Проверка:

expect(filterMock).toHaveBeenCalled();

Изоляция тестов

При мокировании важно сбрасывать состояние между тестами.

Пример:

beforeEach(() => {
  jest.clearAllMocks();
});

Или:

afterEach(() => {
  jest.restoreAllMocks();
});

Это предотвращает влияние предыдущих тестов.


Мокирование сторонних библиотек

Muuri может использоваться вместе с:

  • React
  • Vue
  • Angular

В таких случаях мокируются:

  • lifecycle-хуки
  • контейнеры компонентов
  • DOM-рендеринг

Пример для React-теста:

jest.mock('muuri');

Можно вернуть поддельную реализацию:

Muuri.mockImplementation(() => ({
  add: jest.fn(),
  remove: jest.fn(),
  layout: jest.fn()
}));

Лучшие практики мокирования

Минимизация моков

Чрезмерное количество моков делает тесты хрупкими.

Мокируются только внешние зависимости.


Тестирование поведения, а не реализации

Проверяется:

  • результат layout
  • вызовы событий
  • изменение состояния

А не внутренние детали реализации.


Детерминированность

Все источники случайности должны быть замокированы:

  • размеры
  • таймеры
  • события

Очистка среды

Каждый тест должен запускаться в чистой среде:

  • новый контейнер
  • новая сетка
  • сброшенные моки

Практический пример полного мокирования

Тест добавления элемента.

jest.useFakeTimers();

Element.prototype.getBoundingClientRect = jest.fn(() => ({
  width: 100,
  height: 100,
  top: 0,
  left: 0,
  right: 100,
  bottom: 100
}));

test('item addition triggers layout', () => {
  const container = document.createElement('div');

  const grid = new Muuri(container);

  const layoutSpy = jest.spyOn(grid, 'layout');

  const item = document.createElement('div');

  grid.add(item);

  expect(layoutSpy).toHaveBeenCalled();
});

Здесь замокированы:

  • размеры элементов
  • анимационные таймеры
  • метод layout

Тест выполняется быстро и полностью контролируемо.