hanbi
Version:
A small javascript library for stubbing and spying on methods/functions.
177 lines (176 loc) • 4.45 kB
JavaScript
/**
* Represents a single stubbed function
*/
export class Stub {
/**
* Whether or not this stub has been called
*/
get called() {
return this._calls.size > 0;
}
/**
* List of all calls this stub has received
*/
get calls() {
return this._calls;
}
/**
* Retrieves an individual call
* @param index Index of the call to retrieve
* @return Call at the specified index
*/
getCall(index) {
return [...this._calls][index];
}
/**
* Retrieves the first call
* @return Call object
*/
get firstCall() {
return this.getCall(0);
}
/**
* Retrieves the last call
* @return Call object
*/
get lastCall() {
return this.getCall(this.callCount - 1);
}
/**
* Number of times this stub has been called
*/
get callCount() {
return this._calls.size;
}
/**
* Constructor
* @param fn Function being stubbed
*/
constructor(fn) {
this._calls = new Set();
this.original = fn;
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
this.handler = function handler(...args) {
// eslint-disable-next-line no-invalid-this
return self._handleCall.call(self, this, args);
};
}
/**
* Processes an individual call to this stub
* @param thisValue Context of the call (`this`)
* @param args Arguments passed when being called
* @return Return value of this call
*/
_handleCall(thisValue, args) {
const returnValue = this._returnFunction
? this._returnFunction.apply(thisValue, args)
: this._returnValue;
this._calls.add({
args: args,
thisValue,
returnValue
});
return returnValue;
}
/**
* Specifies the value this stub should return
* @param val Value to return
*/
returns(val) {
this._returnFunction = undefined;
this._returnValue = val;
}
/**
* Specifies a function to call to retrieve the return value of this
* stub
* @param fn Function to call
*/
callsFake(fn) {
this._returnValue = undefined;
this._returnFunction = fn;
}
/**
* Enables pass-through, in that the original function is called when
* this stub is called.
*/
passThrough() {
this.callsFake(this.original);
}
/**
* Resets call state (e.g. call count, calls, etc.)
*/
reset() {
this._calls.clear();
}
/**
* Restores this stub.
* This behaviour differs depending on what created the stub.
*/
restore() {
if (this.restoreCallback) {
this.restoreCallback();
}
}
/**
* Asserts that the stub was called with a set of arguments
* @param args Arguments to assert for
* @return Whether they were passed or not
*/
calledWith(...args) {
return [...this.calls].some((call) => call.args.length === args.length &&
call.args.every((arg, idx) => args[idx] === arg));
}
/**
* Asserts that the stub returned a given value
* @param val Value to check for
* @return Whether the value was ever returned or not
*/
returned(val) {
return [...this.calls].some((call) => call.returnValue === val);
}
}
const stubbedMethods = new Set();
/**
* Stubs a method of a given object.
* @param obj Object the method belongs to
* @param method Method name to stub
* @return Stubbed method
*/
export function stubMethod(obj, method) {
const instance = new Stub(obj[method]);
obj[method] = instance.handler;
instance.restoreCallback = () => {
obj[method] = instance.original;
stubbedMethods.delete(instance);
};
stubbedMethods.add(instance);
return instance;
}
/**
* Stubs a given function.
* @param fn Function to stub
* @return Stubbed function
*/
export function stub(fn) {
const result = new Stub(fn);
return result;
}
/**
* Creates an anonymous spy.
* @return Anonymous stub
*/
export function spy() {
return new Stub((() => {
return;
}));
}
/**
* Restores all tracked stubs at once.
*/
export function restore() {
for (const stub of stubbedMethods) {
stub.restore();
}
stubbedMethods.clear();
}