Концепция и назначение spies

В тестировании на JavaScript spies представляют собой инструмент для наблюдения за функциями без изменения их исходной логики. Они позволяют отслеживать вызовы функций, аргументы, с которыми функции были вызваны, количество вызовов, а также управлять поведением функций в тестовой среде. Spies особенно полезны для тестирования взаимодействий между объектами и модульных тестов, где необходимо изолировать зависимые компоненты.


Создание spy

Jasmine предоставляет несколько способов создания spy. Основные из них:

  1. spyOn
const calculator = {
  add(a, b) {
    return a + b;
  }
};

spyOn(calculator, 'add');
calculator.add(2, 3);

expect(calculator.add).toHaveBeenCalled();
expect(calculator.add).toHaveBeenCalledWith(2, 3);

spyOn заменяет указанную функцию шпионским аналогом, позволяя отслеживать её вызовы. Фактический результат функции по умолчанию не изменяется, если не указано иное.

  1. jasmine.createSpy
const spyFunction = jasmine.createSpy('mySpy');
spyFunction('test');

expect(spyFunction).toHaveBeenCalled();
expect(spyFunction).toHaveBeenCalledWith('test');

createSpy создаёт полностью автономный шпион, который не связан с конкретным объектом. Используется для замены функций, которые ещё не реализованы или зависят от внешних сервисов.

  1. jasmine.createSpyObj
const serviceSpy = jasmine.createSpyObj('Service', ['getData', 'setData']);
serviceSpy.getData();
expect(serviceSpy.getData).toHaveBeenCalled();

createSpyObj создаёт объект с набором шпионов, что удобно для имитации сложных сервисов или модулей с множественными методами.


Управление поведением spies

По умолчанию шпион только отслеживает вызовы функции. Для имитации поведения функции используются специальные методы:

  • and.callThrough() – вызывает оригинальную функцию. Полезно, когда требуется сохранить её исходное поведение.
spyOn(calculator, 'add').and.callThrough();
expect(calculator.add(1, 2)).toBe(3);
  • and.returnValue(value) – возвращает заданное значение при каждом вызове.
spyOn(calculator, 'add').and.returnValue(10);
expect(calculator.add(1, 2)).toBe(10);
  • and.callFake(fn) – заменяет функцию на пользовательскую реализацию.
spyOn(calculator, 'add').and.callFake((a, b) => a * b);
expect(calculator.add(2, 3)).toBe(6);
  • and.throwError(message) – вызывает ошибку при вызове функции.
spyOn(calculator, 'add').and.throwError('Ошибка!');
expect(() => calculator.add(1, 2)).toThrowError('Ошибка!');

Проверка вызовов spies

Jasmine предоставляет набор матчеров для проверки вызовов шпионов:

  • toHaveBeenCalled() – проверяет, что функция была вызвана хотя бы один раз.
  • toHaveBeenCalledTimes(number) – проверяет точное количество вызовов.
  • toHaveBeenCalledWith(...args) – проверяет, что функция была вызвана с указанными аргументами.
  • toHaveBeenCalledOnceWith(...args) – проверяет однократный вызов с конкретными аргументами.
  • toHaveBeenCalledBefore(spy) – проверяет порядок вызовов между шпионами.
  • toHaveBeenCalledAfter(spy) – аналогично, но для проверки вызова после другого шпиона.

Пример комплексной проверки:

spyOn(calculator, 'add');
spyOn(calculator, 'subtract');

calculator.add(5, 3);
calculator.subtract(2, 1);

expect(calculator.add).toHaveBeenCalledBefore(calculator.subtract);
expect(calculator.subtract).toHaveBeenCalledWith(2, 1);
expect(calculator.add).toHaveBeenCalledTimes(1);

Использование spies для изоляции зависимостей

В модульном тестировании важно изолировать тестируемый код от внешних зависимостей. Spies позволяют:

  1. Отслеживать вызовы внешних сервисов без их фактического выполнения.
  2. Управлять возвращаемыми значениями для проверки различных сценариев.
  3. Тестировать обработку ошибок, подменяя поведение функций на выброс исключений.

Пример имитации API-сервиса:

const apiService = {
  fetchData() {
    // Реальный HTTP-запрос
  }
};

spyOn(apiService, 'fetchData').and.returnValue(Promise.resolve({ data: 'test' }));

myFunctionUsingAPI(apiService).then(result => {
  expect(result).toBe('test');
});

Важные практики при работе с spies

  • Использовать spyOn только для существующих методов объектов. Для несуществующих методов — createSpy или createSpyObj.
  • При использовании callThrough сохраняется оригинальная логика функции, что полезно для интеграционных тестов.
  • Всегда сбрасывать шпионы при необходимости, чтобы избежать конфликтов между тестами:
afterEach(() => {
  jasmine.restoreAllSpies();
});
  • Комбинировать callFake и returnValue для моделирования сложных сценариев без фактического выполнения зависимостей.

Spies в Jasmine обеспечивают мощный механизм контроля функций и методов, позволяя полностью управлять их поведением и проверять взаимодействие компонентов на всех уровнях тестирования. Они являются ключевым инструментом для создания надёжных, изолированных и предсказуемых тестов в JavaScript.