0.0.2 • Published 5 months ago

@e22m4u/js-spy v0.0.2

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

@e22m4u/js-spy

Утилита слежения за вызовом функций и методов для JavaScript. Позволяет создавать "шпионов" для функций или методов объектов, отслеживать их вызовы, аргументы, возвращаемые значения, выброшенные ошибки, а также управлять группой шпионов.

Содержание

Установка

npm install @e22m4u/js-spy

Поддержка ESM и CommonJS стандартов.

ESM

import {createSpy, createSpiesGroup} from '@e22m4u/js-spy';

CommonJS

const {createSpy, createSpiesGroup} = require('@e22m4u/js-spy');

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

Отслеживание вызова функции:

import {createSpy} from '@e22m4u/js-spy';

function greet(name) {
  return `Hello, ${name}!`;
}

const greetSpy = createSpy(greet);

greetSpy('World');
greetSpy('JavaScript');

console.log(greetSpy.called);    // true
console.log(greetSpy.callCount); // 2

// аргументы вызова
console.log(greetSpy.getCall(0).args); // ['World']
console.log(greetSpy.getCall(1).args); // ['JavaScript']

// возвращаемое значение
console.log(greetSpy.getCall(0).returnValue); // 'Hello, World!'
console.log(greetSpy.getCall(1).returnValue); // 'Hello, JavaScript!'

// тест аргументов
console.log(greetSpy.calledWith('World'));      // true
console.log(greetSpy.calledWith('JavaScript')); // true
console.log(greetSpy.calledWith('FooBar'));     // false

// тест аргументов определенного вызова
console.log(greetSpy.nthCalledWith(0, 'World'));      // true
console.log(greetSpy.nthCalledWith(1, 'JavaScript')); // true
console.log(greetSpy.nthCalledWith(1, 'FooBar'));     // false

// тест возвращаемого значения
console.log(greetSpy.nthCallReturned(0, 'Hello, World'));      // true
console.log(greetSpy.nthCallReturned(1, 'Hello, JavaScript')); // true

try {
  greetSpy.getCall(5); // Попытка получить несуществующий вызов
} catch (e) {
  // Ожидаемая ошибка, например:
  // "Invalid call index 5. Spy has 2 call(s)."
  console.error(e.message);
}

Отслеживание вызова метода:

import {createSpy} from '@e22m4u/js-spy';

const calculator = {
  value: 0,
  add(a, b) {
    this.value = a + b;
    return this.value;
  },
};

const addSpy = createSpy(calculator, 'add');

calculator.add(5, 3);
console.log(calculator.value); // 8

calculator.add(2, 1);
console.log(calculator.value); // 3

console.log(addSpy.called);    // true
console.log(addSpy.callCount); // 2

// аргументы вызова
console.log(addSpy.getCall(0).args); // [5, 3]
console.log(addSpy.getCall(1).args); // [2, 1]

// возвращаемое значение
console.log(addSpy.getCall(0).returnValue); // 8
console.log(addSpy.getCall(1).returnValue); // 3

// контекст вызова
console.log(addSpy.getCall(0).thisArg === calculator); // true
console.log(addSpy.getCall(1).thisArg === calculator); // true

// тест аргументов
console.log(addSpy.calledWith(5, 3));  // true
console.log(addSpy.calledWith(2, 1));  // true
console.log(addSpy.calledWith('foo')); // false

// тест аргументов определенного вызова
console.log(addSpy.nthCalledWith(0, 5, 3));  // true
console.log(addSpy.nthCalledWith(1, 2, 1));  // true
console.log(addSpy.nthCalledWith(1, 'foo')); // false

// тест возвращаемого значения
console.log(addSpy.nthCallReturned(0, 8)); // true
console.log(addSpy.nthCallReturned(1, 3)); // true

// восстановление оригинального метода
addSpy.restore();
// calculator.add теперь снова оригинальный метод

Управление группой шпионов (SpiesGroup)

Иногда бывает удобно управлять несколькими шпионами одновременно, например, восстановить их все разом. Для этого используется SpiesGroup.

import {createSpiesGroup} from '@e22m4u/js-spy';

// объект с методами для отслеживания
const service = {
  fetchData(id) {
    console.log(`Fetching data for ${id}...`);
    return {id, data: `Data for ${id}`};
  },
  processItem(item) {
    console.log(`Processing ${item.data}...`);
    return `Processed: ${item.data}`;
  }
};

// одиночная функция для отслеживания
function standaloneLogger(message) {
  console.log(`LOG: ${message}`);
}

// создание группы
const group = createSpiesGroup();

// добавление шпионов в группу:
//   метод group.on() работает аналогично createSpy(),
//   но добавляет шпиона в группу и возвращает созданного
//   шпиона
const fetchDataSpy = group.on(service, 'fetchData');
const processItemSpy = group.on(service, 'processItem');
const loggerSpy = group.on(standaloneLogger);

// вызов отслеживаемых функций/методов
const data = service.fetchData(1);
service.processItem(data);
standaloneLogger('All done!');

console.log(fetchDataSpy.callCount);   // 1
console.log(processItemSpy.callCount); // 1
console.log(loggerSpy.callCount);      // 1

// восстановление всех шпионов в группе:
//   - оригинальные методы service.fetchData
//     и service.processItem будут восстановлены
//   - история вызовов (callCount, called, getCall и т.д.)
//     для fetchDataSpy, processItemSpy и loggerSpy
//     будет сброшена
//   - внутренний список шпионов в группе будет очищен
group.restore();

console.log(service.fetchData === fetchDataSpy);
// false (оригинальный метод восстановлен)
console.log(fetchDataSpy.callCount);
// 0 (история сброшена)
console.log(loggerSpy.called);
// false (история сброшена)

API

createSpy(target, methodNameOrImpl, customImplForMethod)

Основная функция для создания шпиона.

Сигнатуры вызова и аргументы:

  1. Отслеживание отдельной функции:
    createSpy(targetFn, [customImplementation])

    • targetFn: Функция, которую требуется отслеживать.
    • customImplementation (необязательно): Пользовательская функция, которая будет вызываться вместо targetFn. Должна иметь ту же сигнатуру.
  2. Отслеживание метода объекта:
    createSpy(targetObject, methodName, [customImplementation])

    • targetObject: Объект, метод которого будет отслеживаться.
    • methodName: Имя метода в targetObject, который требуется отслеживать.
    • customImplementation (необязательно): Пользовательская функция, которая будет вызываться вместо оригинального метода. Должна иметь ту же сигнатуру.

Возвращает:

  • Функция-шпион с дополнительными свойствами и методами для инспекции.

Свойства и методы шпиона

Каждая функция-шпион, возвращаемая createSpy (или group.on), обладает следующими свойствами и методами:

spy(...args)

Сам шпион является функцией. При вызове он выполняет либо оригинальную функцию/метод (или пользовательскую реализацию, если предоставлена), записывает информацию о вызове и возвращает результат (или пробрасывает ошибку).

const fn = (x) => x * 2;
const spy = createSpy(fn);

const result = spy(5);      // result будет 10
console.log(spy.callCount); // 1

spy.called

  • Тип: boolean (только для чтения)
  • Описание: Указывает, был ли шпион вызван хотя бы один раз.
const spy = createSpy(() => {});
console.log(spy.called); // false
spy();
console.log(spy.called); // true

spy.callCount

  • Тип: number (только для чтения)
  • Описание: Количество раз, которое шпион был вызван.
const spy = createSpy(() => {});
console.log(spy.callCount); // 0
spy();
spy();
console.log(spy.callCount); // 2

spy.getCall(n)

Аргументы:

  • n: Число, индекс вызова (начиная с 0).

Возвращает: Объект CallInfo со свойствами:

  • args: Массив аргументов, с которыми был совершен вызов.
  • thisArg: Контекст this вызова.
  • returnValue: Значение, возвращенное функцией (или undefined, если функция бросила ошибку).
  • error: Ошибка, выброшенная функцией (или undefined, если ошибки не было).

Выбрасывает:

  • RangeError: Если n не является допустимым индексом вызова.

Пример:

const spy = createSpy((a, b) => a + b);
spy.call({ id: 1 }, 10, 20); // 0-й вызов

const firstCall = spy.getCall(0);
console.log(firstCall.args); // [10, 20]

try {
  spy.getCall(1); // Попытка получить несуществующий вызов
} catch (e) {
  // Ожидаемая ошибка, например:
  // "Invalid call index 1. Spy has 1 call(s)."
  console.error(e.message);
}

spy.calledWith(...expectedArgs)

Аргументы:

  • ...expectedArgs: Аргументы, с которыми, как ожидается, был вызван шпион.

Возвращает: boolean

  • true, если шпион был хотя бы раз вызван с точно таким же набором аргументов (сравнение с использованием Object.is).
  • false в противном случае.

Пример:

const spy = createSpy(() => {});
spy(1, 'a', true);
spy(2, 'b');

console.log(spy.calledWith(1, 'a', true)); // true
console.log(spy.calledWith(1, 'a'));       // false
console.log(spy.calledWith(2, 'c'));       // false

spy.nthCalledWith(n, ...expectedArgs)

Аргументы:

  • n: Число, индекс вызова (начиная с 0).
  • ...expectedArgs: Аргументы, с которыми, как ожидается, был совершен n-ый вызов.

Возвращает: boolean

  • true, если n-ый вызов шпиона был совершен с точно таким же набором аргументов.
  • false в противном случае.

Выбрасывает:

  • RangeError: Если n не является допустимым индексом вызова (унаследовано от getCall).

Пример:

const spy = createSpy(() => {});
spy('first call');
spy('second call', 123);

console.log(spy.nthCalledWith(0, 'first call'));       // true
console.log(spy.nthCalledWith(1, 'second call', 123)); // true
console.log(spy.nthCalledWith(0, 'another'));          // false

try {
  spy.nthCalledWith(2, 'anything'); // Несуществующий вызов
} catch (e) {
  // Ожидаемая ошибка, например:
  // "Invalid call index 2. Spy has 2 call(s)."
  console.error(e.message);
}

spy.nthCallReturned(n, expectedReturnValue)

Аргументы:

  • n: Число, индекс вызова (начиная с 0).
  • expectedReturnValue: Ожидаемое возвращаемое значение для n-го вызова.

Возвращает: boolean

  • true, если n-ый вызов шпиона вернул expectedReturnValue (сравнение с помощью Object.is).
  • false, если значение не совпало или вызов выбросил ошибку.

Выбрасывает:

  • RangeError: Если n не является допустимым индексом вызова (унаследовано от getCall).

Пример:

const spy = createSpy(val => {
  if (val === 0) throw new Error('zero');
  return val * 10;
});
spy(5); // 0-й вызов, возвращает 50
try { spy(0); } catch(e) {} // 1-й вызов, бросает ошибку
spy(2); // 2-й вызов, возвращает 20

console.log(spy.nthCallReturned(0, 50)); // true
console.log(spy.nthCallReturned(1, 10)); // false (вызов 1 бросил ошибку)
console.log(spy.nthCallReturned(2, 20)); // true

try {
  spy.nthCallReturned(3, 30); // Несуществующий вызов
} catch (e) {
  // Ожидаемая ошибка, например:
  // "Invalid call index 3. Spy has 3 call(s)."
  console.error(e.message);
}

spy.nthCallThrew(n, expectedError)

Аргументы:

  • n: Число, индекс вызова (начиная с 0).
  • expectedError (необязательно): Матчер для ошибки. Возможные варианты:
    • undefined: Проверяет, что n-ый вызов просто выбросил любую ошибку.
    • Строка: Проверяет, что сообщение выброшенной ошибки (error.message) совпадает со строкой.
    • Конструктор ошибки (например, Error, TypeError): Проверяет, что выброшенная ошибка является экземпляром (instanceof) этого конструктора.
    • Экземпляр ошибки: Проверяет, что имя (error.name) и сообщение (error.message) выброшенной ошибки совпадают с полями expectedError.
    • Любое другое значение: Проверяется прямое совпадение выброшенной ошибки с expectedError через Object.is.

Возвращает: boolean

  • true, если n-ый вызов шпиона выбросил ошибку или ошибка соответствует expectedError.
  • false, если вызов не выбросил ошибку (или выбросил не ту ошибку).

Выбрасывает:

  • RangeError: Если n не является допустимым индексом вызова (унаследовано от getCall).

Пример:

const mightThrow = (val) => {
  if (val === 0) throw new TypeError('Zero is not allowed');
  if (val < 0) throw new Error('Negative value');
  return val;
};
const spy = createSpy(mightThrow);

try { spy(0); } catch (e) {}  // 0-й вызов
try { spy(-5); } catch (e) {} // 1-й вызов
spy(10);                      // 2-й вызов

console.log(spy.nthCallThrew(0));                        // true
console.log(spy.nthCallThrew(0, 'Zero is not allowed')); // true
console.log(spy.nthCallThrew(2));                        // false (2-й вызов не бросил ошибку)

try {
  spy.nthCallThrew(3); // Несуществующий вызов
} catch (e) {
  // Ожидаемая ошибка, например:
  // "Invalid call index 3. Spy has 3 call(s)."
  console.error(e.message);
}

spy.restore()

Описание:

  • Восстанавливает оригинальный метод, если шпион был создан для метода объекта.
  • Сбрасывает историю вызовов шпиона (callCount становится 0, called становится false, и все записи о вызовах очищаются).
  • Если шпион был создан для отдельной функции (а не для метода объекта), восстановление метода не происходит (так как нечего восстанавливать), но история вызовов все равно сбрасывается.
// для метода объекта
const myObject = {
  doSomething() {
    return 'original';
  }
};

const methodSpy = createSpy(myObject, 'doSomething');
// вызов шпиона
myObject.doSomething();
console.log(methodSpy.callCount); // 1

// восстановление метода
methodSpy.restore();
console.log(myObject.doSomething()); // 'original' (метод восстановлен)
console.log(methodSpy.callCount);    // 0 (история сброшена)

// для отдельной функции
const fn = () => 'result';
const fnSpy = createSpy(fn);
fnSpy();
console.log(fnSpy.callCount); // 1

// сброс истории функции
fnSpy.restore();
console.log(fnSpy.callCount); // 0 (история сброшена)

createSpiesGroup()

Фабричная функция для создания экземпляра SpiesGroup.

Возвращает:

  • Новый экземпляр SpiesGroup.
import {createSpiesGroup} from '@e22m4u/js-spy';
const group = createSpiesGroup();

Методы SpiesGroup

Экземпляр SpiesGroup имеет следующие методы:

group.on(target, methodNameOrImpl, customImplForMethod)

Создает шпиона (используя createSpy с теми же аргументами) и добавляет его в группу.

Сигнатуры вызова и аргументы идентичны createSpy:

  1. group.on(targetFn, [customImplementation])
  2. group.on(targetObject, methodName, [customImplementation])

Возвращает:

  • Созданную функцию-шпион (такую же, как вернул бы createSpy).

Пример:

const group = createSpiesGroup();
const obj = {greet: () => 'Hello'};

const greetSpy = group.on(obj, 'greet');
// obj.greet теперь шпион, и greetSpy добавлен в группу
obj.greet();
console.log(greetSpy.called); // true

group.restore()

Вызывает метод restore() для каждого шпиона, содержащегося в группе. Это означает, что:

  • Все оригинальные методы объектов, для которых были созданы шпионы в этой группе, будут восстановлены.
  • История вызовов всех шпионов в группе будет сброшена.
  • Внутренний список шпионов в самой группе будет очищен, делая группу готовой к повторному использованию (если необходимо).

Возвращает:

  • this (экземпляр SpiesGroup) для возможной цепочки вызовов.

Пример:

const group = createSpiesGroup();

// объект с методом для отслеживания
const service = {
  process() { /* ... */ }
};

// одиночная функция для отслеживания
function utilFn() { /* ... */ }

// создание шпионов
const processSpy = group.on(service, 'process');
const utilSpy = group.on(utilFn);

// вызов отслеживаемого метода и функции
// для изменения истории вызовов
service.process();
utilFn();

// проверка количества вызовов
console.log(processSpy.callCount); // 1
console.log(utilSpy.callCount);    // 1

// восстановление шпионов
// и сброс истории
group.restore();

// service.process теперь оригинальный метод
console.log(processSpy.callCount); // 0
console.log(utilSpy.callCount);    // 0
console.log(group.spies.length);   // 0

Тесты

npm run test

Лицензия

MIT

0.0.2

5 months ago

0.0.1

6 months ago