发行
Spies的工作原理是在跟踪调用和返回值的原始函数周围创建一个包装函数。间谍只能记录通过它的通话。
如果递归函数直接调用自身,则无法将该调用包装到间谍中。
解决方案
递归函数调用自身的方式必须与从外部调用自身的方式相同。然后,当函数包装在spy中时,递归调用包装在同一spy中。
例1:类方法
递归类方法使用
this
指的是他们的类实例。当实例方法被间谍替换时,递归调用会自动调用同一间谍:
class MyClass {
fibonacci(n) {
if (n < 0) throw new Error('must be 0 or greater');
if (n === 0) return 0;
if (n === 1) return 1;
return this.fibonacci(n - 1) + this.fibonacci(n - 2);
}
}
describe('fibonacci', () => {
const instance = new MyClass();
it('should calculate Fibonacci numbers', () => {
expect(instance.fibonacci(5)).toBe(5);
expect(instance.fibonacci(10)).toBe(55);
});
it('can be spied on', () => {
const spy = sinon.spy(instance, 'fibonacci');
instance.fibonacci(10);
expect(spy.callCount).toBe(177); // PASSES
spy.restore();
});
});
注意
:类方法使用
这
所以为了调用spied函数
spy(10);
而不是
instance.fibonacci(10);
该函数需要转换为箭头函数,或者显式绑定到
this.fibonacci = this.fibonacci.bind(this);
在类构造函数中。
例2:模块
如果模块内的递归函数使用该模块调用自身,则该函数可以监视。当模块函数被间谍替换时,递归调用会自动调用同一间谍:
ES6课程
// ---- lib.js ----
import * as lib from './lib';
export const fibonacci = (n) => {
if (n < 0) throw new Error('must be 0 or greater');
if (n === 0) return 0;
if (n === 1) return 1;
// call fibonacci using lib
return lib.fibonacci(n - 1) + lib.fibonacci(n - 2);
};
// ---- lib.test.js ----
import * as sinon from 'sinon';
import * as lib from './lib';
describe('fibonacci', () => {
it('should calculate Fibonacci numbers', () => {
expect(lib.fibonacci(5)).toBe(5);
expect(lib.fibonacci(10)).toBe(55);
});
it('should call itself recursively', () => {
const spy = sinon.spy(lib, 'fibonacci');
spy(10);
expect(spy.callCount).toBe(177); // PASSES
spy.restore();
});
});
通用.js
// ---- lib.js ----
exports.fibonacci = (n) => {
if (n < 0) throw new Error('must be 0 or greater');
if (n === 0) return 0;
if (n === 1) return 1;
// call fibonacci using exports
return exports.fibonacci(n - 1) + exports.fibonacci(n - 2);
}
// ---- lib.test.js ----
const sinon = require('sinon');
const lib = require('./lib');
describe('fibonacci', () => {
it('should calculate Fibonacci numbers', () => {
expect(lib.fibonacci(5)).toBe(5);
expect(lib.fibonacci(10)).toBe(55);
});
it('should call itself recursively', () => {
const spy = sinon.spy(lib, 'fibonacci');
spy(10);
expect(spy.callCount).toBe(177); // PASSES
spy.restore();
});
});
例3:对象包装器
如果将不属于模块的独立递归函数放在包装对象中并使用该对象调用自身,则该函数可以成为可监视函数。当对象中的函数被间谍替换时,递归调用会自动调用同一间谍:
const wrapper = {
fibonacci: (n) => {
if (n < 0) throw new Error('must be 0 or greater');
if (n === 0) return 0;
if (n === 1) return 1;
// call fibonacci using the wrapper
return wrapper.fibonacci(n - 1) + wrapper.fibonacci(n - 2);
}
};
describe('fibonacci', () => {
it('should calculate Fibonacci numbers', () => {
expect(wrapper.fibonacci(5)).toBe(5);
expect(wrapper.fibonacci(10)).toBe(55);
expect(wrapper.fibonacci(15)).toBe(610);
});
it('should call itself recursively', () => {
const spy = sinon.spy(wrapper, 'fibonacci');
spy(10);
expect(spy.callCount).toBe(177); // PASSES
spy.restore();
});
});