Spy matchers

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


Создание Spy

Spy создаётся с помощью функций spyOn или jasmine.createSpy.

let calculator = {
  add: (a, b) => a + b
};

spyOn(calculator, 'add');

calculator.add(2, 3);

expect(calculator.add).toHaveBeenCalled();
  • spyOn(object, 'method') — заменяет метод объекта на spy, сохраняя возможность отслеживания вызовов.
  • jasmine.createSpy('имя') — создаёт отдельную функцию-Spy без привязки к объекту.
let spyFn = jasmine.createSpy('mySpy');
spyFn('argument');
expect(spyFn).toHaveBeenCalledWith('argument');

Основные Spy Matchers

1. toHaveBeenCalled

Проверяет, был ли Spy вызван хотя бы один раз.

expect(spy).toHaveBeenCalled();

2. toHaveBeenCalledTimes

Позволяет проверить, сколько раз функция была вызвана.

expect(spy).toHaveBeenCalledTimes(3);

3. toHaveBeenCalledWith

Проверяет, что Spy был вызван с определёнными аргументами хотя бы один раз.

expect(spy).toHaveBeenCalledWith(1, 2, 3);

4. toHaveBeenCalledOnceWith

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

expect(spy).toHaveBeenCalledOnceWith('test');

5. toHaveBeenCalledBefore и toHaveBeenCalledAfter

Используются для проверки порядка вызовов нескольких spy.

let spyA = jasmine.createSpy('A');
let spyB = jasmine.createSpy('B');

spyA();
spyB();

expect(spyA).toHaveBeenCalledBefore(spyB);
expect(spyB).toHaveBeenCalledAfter(spyA);

6. toHaveBeenCalledWith(jasmine.any(Constructor))

Позволяет проверять тип аргумента, не заботясь о его точном значении.

expect(spy).toHaveBeenCalledWith(jasmine.any(Number));
expect(spy).toHaveBeenCalledWith(jasmine.any(String));

7. toHaveBeenCalledWith(jasmine.objectContaining({...}))

Проверяет, что переданный объект содержит указанные ключи и значения, игнорируя остальные свойства.

expect(spy).toHaveBeenCalledWith(jasmine.objectContaining({ id: 1, name: 'Test' }));

Настройка Spy для возврата значений

Spy можно заставить возвращать определённое значение или бросать исключение.

spyOn(calculator, 'add').and.returnValue(10);
expect(calculator.add(2, 3)).toBe(10);

spyOn(calculator, 'add').and.throwError('Ошибка!');
expect(() => calculator.add(1,2)).toThrowError('Ошибка!');
  • .and.callThrough() — позволяет вызвать оригинальную функцию, сохранив возможность отслеживания.
  • .and.callFake(fn) — заменяет метод на свою функцию с кастомной логикой.

Проверка последовательности вызовов и аргументов

Jasmine сохраняет всю историю вызовов spy, что позволяет делать комплексные проверки:

spy('first');
spy('second');

expect(spy.calls.count()).toBe(2);
expect(spy.calls.argsFor(0)).toEqual(['first']);
expect(spy.calls.argsFor(1)).toEqual(['second']);
expect(spy.calls.allArgs()).toEqual([['first'], ['second']]);
  • spy.calls.count() — количество вызовов.
  • spy.calls.argsFor(n) — аргументы конкретного вызова.
  • spy.calls.allArgs() — массив всех аргументов каждого вызова.
  • spy.calls.mostRecent() — аргументы последнего вызова.
  • spy.calls.first() — аргументы первого вызова.

Эта функциональность позволяет детально анализировать поведение функций и их взаимодействие с другими объектами.


Асинхронные Spy и ожидания

Spy можно использовать с асинхронными функциями. Например, для проверки вызова после промиса:

let spy = jasmine.createSpy('asyncSpy');
Promise.resolve().then(() => spy());

setTimeout(() => {
  expect(spy).toHaveBeenCalled();
}, 0);

Для более точного контроля Jasmine поддерживает fake timers и async/await.

jasmine.clock().install();

let spy = jasmine.createSpy('delayedSpy');
setTimeout(() => spy(), 1000);

jasmine.clock().tick(1000);
expect(spy).toHaveBeenCalled();

jasmine.clock().uninstall();

Практические советы при работе с Spy Matchers

  • Использовать spyOn для существующих методов объектов, чтобы сохранить контекст this.
  • Для внешних функций, модулей и коллбеков лучше применять jasmine.createSpy.
  • Для сложных объектов использовать jasmine.objectContaining и jasmine.any — это облегчает тестирование частичных совпадений.
  • Проверка порядка вызовов (toHaveBeenCalledBefore/After) помогает гарантировать правильное взаимодействие модулей в больших приложениях.
  • Комбинировать проверку аргументов и количества вызовов для полной уверенности в корректной логике кода.

Spy matchers в Jasmine позволяют не только проверять результат выполнения функций, но и детально анализировать поведение кода, обеспечивая высокую точность тестов и уверенность в работе приложения.