Spy properties

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


Создание Spy

Существует несколько способов создания spy в Jasmine:

  1. spyOn(obj, ‘method’) — создаёт spy для существующего метода объекта.
const calculator = {
    add(a, b) {
        return a + b;
    }
};

spyOn(calculator, 'add');
calculator.add(2, 3);
expect(calculator.add).toHaveBeenCalled();
  1. jasmine.createSpy(‘spyName’) — создаёт отдельный spy, который можно передавать как колбэк или использовать в тестах.
const mySpy = jasmine.createSpy('mySpy');
mySpy('hello');
expect(mySpy).toHaveBeenCalledWith('hello');

Основные свойства spy

После создания spy, объект функции получает множество свойств для анализа.

  • calls — объект, содержащий информацию о всех вызовах spy.

    • calls.any() — возвращает true, если spy был вызван хотя бы один раз.
    • calls.count() — количество вызовов.
    • calls.argsFor(index) — аргументы вызова с указанным индексом.
    • calls.allArgs() — массив всех аргументов для всех вызовов.
    • calls.all() — массив объектов вызовов с аргументами и контекстом (this).
    • calls.mostRecent() — объект последнего вызова.
    • calls.first() — объект первого вызова.
    • calls.reset() — сбрасывает всю историю вызовов spy.

Пример использования:

const obj = {
    multiply(a, b) { return a * b; }
};

spyOn(obj, 'multiply');
obj.multiply(2, 3);
obj.multiply(4, 5);

expect(obj.multiply.calls.count()).toBe(2);
expect(obj.multiply.calls.argsFor(0)).toEqual([2, 3]);
expect(obj.multiply.calls.mostRecent().args).toEqual([4, 5]);

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

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

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

Пример:

const service = {
    fetchData() { return 'data'; }
};

spyOn(service, 'fetchData').and.returnValue('mocked data');
expect(service.fetchData()).toBe('mocked data');

spyOn(service, 'fetchData').and.callThrough();
expect(service.fetchData()).toBe('data');

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

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

  • toHaveBeenCalled() — был ли вызов хотя бы один раз.
  • toHaveBeenCalledTimes(n) — проверка точного числа вызовов.
  • toHaveBeenCalledWith(...args) — проверка вызова с конкретными аргументами.
  • toHaveBeenCalledOnceWith(...args) — был ли вызов один раз с указанными аргументами.
  • toHaveBeenCalledBefore(otherSpy) — проверка порядка вызовов.
  • toHaveBeenCalledAfter(otherSpy) — обратный порядок вызовов.

Пример:

const logger = {
    log(msg) { console.log(msg); }
};

spyOn(logger, 'log');
logger.log('first');
logger.log('second');

expect(logger.log).toHaveBeenCalledTimes(2);
expect(logger.log).toHaveBeenCalledWith('first');
expect(logger.log.calls.first().args[0]).toBe('first');
expect(logger.log.calls.mostRecent().args[0]).toBe('second');

Контекст вызова и this

Spy сохраняет контекст вызова (this), что важно при тестировании методов объектов:

const user = {
    name: 'Alice',
    greet() { return `Hello, ${this.name}`; }
};

spyOn(user, 'greet').and.callThrough();
expect(user.greet()).toBe('Hello, Alice');
expect(user.greet.calls.mostRecent().object).toBe(user);

Свойство object внутри calls показывает объект, через который был вызван spy.


Сброс и повторное использование Spy

Историю вызовов можно сбросить, чтобы повторно использовать spy без влияния предыдущих вызовов:

const fn = jasmine.createSpy('fn');
fn(1);
fn.calls.reset();
expect(fn.calls.any()).toBe(false);

Применение в комплексных тестах

Spy особенно полезны при тестировании асинхронного кода, колбэков, событий и зависимостей. Например, при тестировании HTTP-запросов можно заменить реальный сервис spy, возвращающий моковые данные, и проверять взаимодействие с ним:

const api = {
    getUser(id) { return fetch(`/user/${id}`); }
};

spyOn(api, 'getUser').and.callFake(id => Promise.resolve({ id, name: 'Test' }));

api.getUser(1).then(user => {
    expect(user.name).toBe('Test');
});

Использование spy properties позволяет полностью контролировать тестируемые функции: фиксировать вызовы, аргументы, контекст, изменять поведение и анализировать последовательность взаимодействий между компонентами.