Mocking модульных зависимостей

Мокирование (mocking) — это техника, используемая в тестировании для изоляции части системы от её зависимостей. В Jasmine, как и в других фреймворках для тестирования, мокирование позволяет имитировать поведение зависимых модулей или объектов, которые могут быть слишком сложными, медленными или изменяющими состояние системы. Этот подход помогает фокусироваться на тестировании функционала в изоляции, без необходимости взаимодействовать с внешними сервисами, базами данных или API.

Основы мокирования

Мокирование в Jasmine реализуется с помощью spies — специальных объектов, которые подменяют реальные функции или методы. Spy позволяет отслеживать, как и с какими аргументами была вызвана функция, сколько раз она была вызвана и какой результат возвращён.

Создание простого spy:

const spy = jasmine.createSpy('spyName');
spy();
expect(spy).toHaveBeenCalled();

В примере выше создаётся простой spy, который ничего не возвращает и используется для проверки, был ли он вызван.

Мокирование функций и методов

Часто тестируемые модули зависят от внешних функций или сервисов. Чтобы изолировать тестируемый код от таких зависимостей, можно использовать Jasmine для замены реальных функций на mock-функции. Это позволяет точно контролировать поведение зависимостей и предотвращать нежелательные побочные эффекты.

Пример мокирования метода объекта:

const myObject = {
  fetchData: function() {
    return 'real data';
  }
};

spyOn(myObject, 'fetchData').and.returnValue('mocked data');

expect(myObject.fetchData()).toBe('mocked data');

В этом примере метод fetchData подменяется на мок, который возвращает заранее заданное значение 'mocked data'.

Стандартные методы spy

Jasmine предоставляет несколько стандартных методов для настройки поведения spies:

  • and.returnValue(value) — возвращает указанное значение при вызове.
  • and.callThrough() — вызывает оригинальную функцию, если spy используется для замены реальной функции.
  • and.callFake(fn) — позволяет задать произвольное поведение через функцию fn, которая будет выполнена вместо оригинала.
  • and.throwError(error) — генерирует ошибку при вызове метода.

Пример использования нескольких методов spy:

const someService = {
  fetchData: function() {
    return 'real data';
  }
};

// Создаём spy, который заменяет оригинальный метод
spyOn(someService, 'fetchData')
  .and.callFake(() => {
    return 'mocked data';
  });

expect(someService.fetchData()).toBe('mocked data');

В этом примере используется callFake, чтобы задать свой собственный способ выполнения метода.

Мокирование асинхронных функций

Асинхронные функции требуют особого подхода при тестировании. Jasmine поддерживает работу с асинхронными методами, включая возможность мокирования промисов или коллбеков.

Пример мокирования асинхронной функции с использованием промисов:

const myAsyncService = {
  fetchDataAsync: function() {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve('real data');
      }, 1000);
    });
  }
};

// Мокируем асинхронную функцию
spyOn(myAsyncService, 'fetchDataAsync').and.returnValue(Promise.resolve('mocked data'));

myAsyncService.fetchDataAsync().then((data) => {
  expect(data).toBe('mocked data');
});

Здесь используется метод and.returnValue, чтобы возвратить разрешённый промис с результатом, который мы заранее задали.

Мокирование зависимостей в тестах с использованием beforeEach и afterEach

Для управления состоянием зависимостей и очистки после тестов можно использовать функции Jasmine beforeEach и afterEach. Это удобно для настройки и удаления mock-объектов между тестами.

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

describe('Testing with mocks', () => {
  let mockDependency;

  beforeEach(() => {
    mockDependency = jasmine.createSpyObj('mockDependency', ['fetchData']);
    mockDependency.fetchData.and.returnValue('mocked data');
  });

  it('should test the mocked dependency', () => {
    expect(mockDependency.fetchData()).toBe('mocked data');
  });

  afterEach(() => {
    // Очистка всех spy-объектов
    jasmine.clock().uninstall();
  });
});

Здесь используется createSpyObj, чтобы создать mock-объект с несколькими методами, которые могут быть подменены с помощью Jasmine.

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

Если тестируемый код использует классы или объекты, которые создаются с помощью конструктора, можно замокать их с помощью spyOn.

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

class MyClass {
  fetchData() {
    return 'real data';
  }
}

describe('Mocking class constructors', () => {
  it('should mock the class constructor', () => {
    const spyConstructor = spyOn(window, 'MyClass').and.returnValue({
      fetchData: jasmine.createSpy().and.returnValue('mocked data')
    });

    const instance = new MyClass();
    expect(instance.fetchData()).toBe('mocked data');
  });
});

Этот подход полезен, если необходимо мокировать не только методы объекта, но и сам процесс создания экземпляров класса.

Мокирование модулей с использованием Jasmine и ES6 import/export

В случае работы с модулями, использующими ES6 import/export, мокирование может быть немного более сложным. Для этого можно использовать такие библиотеки, как jest или специальные утилиты для мокирования модулей в Jasmine.

Пример мокирования модуля в Jasmine:

import { myFunction } from './myModule';

describe('Mocking ES6 Modules', () => {
  it('should mock an imported function', () => {
    spyOn(myFunction, 'myMethod').and.returnValue('mocked value');
    expect(myFunction.myMethod()).toBe('mocked value');
  });
});

Здесь требуется интеграция Jasmine с утилитами для мокирования модулей, так как стандартное Jasmine не предоставляет прямую поддержку для модулярных зависимостей.

Заключение

Мокирование модульных зависимостей в Jasmine является мощным инструментом для тестирования, который позволяет изолировать тестируемый код от внешних зависимостей и сосредоточиться на его логике. Использование spy, callFake, и других методов позволяет точно контролировать поведение зависимостей и тестировать их взаимодействие с основным кодом, не влияя на его работу.