UNPKG

sinon

Version:

JavaScript test spies, stubs and mocks.

370 lines (331 loc) 10.1 kB
"use strict"; const arrayProto = require("@sinonjs/commons").prototypes.array; const extend = require("./util/core/extend"); const functionToString = require("./util/core/function-to-string"); const proxyCall = require("./proxy-call"); const proxyCallUtil = require("./proxy-call-util"); const proxyInvoke = require("./proxy-invoke"); const inspect = require("util").inspect; const push = arrayProto.push; const forEach = arrayProto.forEach; const slice = arrayProto.slice; const emptyFakes = Object.freeze([]); // Public API const proxyApi = { toString: functionToString, named: function named(name) { this.displayName = name; const nameDescriptor = Object.getOwnPropertyDescriptor(this, "name"); if (nameDescriptor && nameDescriptor.configurable) { // IE 11 functions don't have a name. // Safari 9 has names that are not configurable. nameDescriptor.value = name; Object.defineProperty(this, "name", nameDescriptor); } return this; }, invoke: proxyInvoke, /* * Hook for derived implementation to return fake instances matching the * given arguments. */ matchingFakes: function (/*args, strict*/) { return emptyFakes; }, getCall: function getCall(index) { let i = index; if (i < 0) { // Negative indices means counting backwards from the last call i += this.callCount; } if (i < 0 || i >= this.callCount) { return null; } return proxyCall( this, this.thisValues[i], this.args[i], this.returnValues[i], this.exceptions[i], this.callIds[i], this.errorsWithCallStack[i], ); }, getCalls: function () { const calls = []; let i; for (i = 0; i < this.callCount; i++) { push(calls, this.getCall(i)); } return calls; }, calledBefore: function calledBefore(proxy) { if (!this.called) { return false; } if (!proxy.called) { return true; } return this.callIds[0] < proxy.callIds[proxy.callIds.length - 1]; }, calledAfter: function calledAfter(proxy) { if (!this.called || !proxy.called) { return false; } return this.callIds[this.callCount - 1] > proxy.callIds[0]; }, calledImmediatelyBefore: function calledImmediatelyBefore(proxy) { if (!this.called || !proxy.called) { return false; } return ( this.callIds[this.callCount - 1] === proxy.callIds[proxy.callCount - 1] - 1 ); }, calledImmediatelyAfter: function calledImmediatelyAfter(proxy) { if (!this.called || !proxy.called) { return false; } return ( this.callIds[this.callCount - 1] === proxy.callIds[proxy.callCount - 1] + 1 ); }, formatters: require("./spy-formatters"), printf: function (format) { const spyInstance = this; const args = slice(arguments, 1); let formatter; return (format || "").replace(/%(.)/g, function (match, specifier) { formatter = proxyApi.formatters[specifier]; if (typeof formatter === "function") { return String(formatter(spyInstance, args)); } else if (!isNaN(parseInt(specifier, 10))) { return inspect(args[specifier - 1]); } return `%${specifier}`; }); }, resetHistory: function () { if (this.invoking) { const err = new Error( "Cannot reset Sinon function while invoking it. " + "Move the call to .resetHistory outside of the callback.", ); err.name = "InvalidResetException"; throw err; } this.called = false; this.notCalled = true; this.calledOnce = false; this.calledTwice = false; this.calledThrice = false; this.callCount = 0; this.firstCall = null; this.secondCall = null; this.thirdCall = null; this.lastCall = null; this.args = []; this.firstArg = null; this.lastArg = null; this.returnValues = []; this.thisValues = []; this.exceptions = []; this.callIds = []; this.errorsWithCallStack = []; if (this.fakes) { forEach(this.fakes, function (fake) { fake.resetHistory(); }); } return this; }, }; const delegateToCalls = proxyCallUtil.delegateToCalls; delegateToCalls(proxyApi, "calledOn", true); delegateToCalls(proxyApi, "alwaysCalledOn", false, "calledOn"); delegateToCalls(proxyApi, "calledWith", true); delegateToCalls( proxyApi, "calledOnceWith", true, "calledWith", false, undefined, 1, ); delegateToCalls(proxyApi, "calledWithMatch", true); delegateToCalls(proxyApi, "alwaysCalledWith", false, "calledWith"); delegateToCalls(proxyApi, "alwaysCalledWithMatch", false, "calledWithMatch"); delegateToCalls(proxyApi, "calledWithExactly", true); delegateToCalls( proxyApi, "calledOnceWithExactly", true, "calledWithExactly", false, undefined, 1, ); delegateToCalls( proxyApi, "calledOnceWithMatch", true, "calledWithMatch", false, undefined, 1, ); delegateToCalls( proxyApi, "alwaysCalledWithExactly", false, "calledWithExactly", ); delegateToCalls( proxyApi, "neverCalledWith", false, "notCalledWith", false, function () { return true; }, ); delegateToCalls( proxyApi, "neverCalledWithMatch", false, "notCalledWithMatch", false, function () { return true; }, ); delegateToCalls(proxyApi, "threw", true); delegateToCalls(proxyApi, "alwaysThrew", false, "threw"); delegateToCalls(proxyApi, "returned", true); delegateToCalls(proxyApi, "alwaysReturned", false, "returned"); delegateToCalls(proxyApi, "calledWithNew", true); delegateToCalls(proxyApi, "alwaysCalledWithNew", false, "calledWithNew"); function createProxy(func, originalFunc) { const proxy = wrapFunction(func, originalFunc); // Inherit function properties: extend(proxy, func); proxy.prototype = func.prototype; extend.nonEnum(proxy, proxyApi); return proxy; } function wrapFunction(func, originalFunc) { const arity = originalFunc.length; let p; // Do not change this to use an eval. Projects that depend on sinon block the use of eval. // ref: https://github.com/sinonjs/sinon/issues/710 switch (arity) { /*eslint-disable no-unused-vars, max-len*/ case 0: p = function proxy() { return p.invoke(func, this, slice(arguments)); }; break; case 1: p = function proxy(a) { return p.invoke(func, this, slice(arguments)); }; break; case 2: p = function proxy(a, b) { return p.invoke(func, this, slice(arguments)); }; break; case 3: p = function proxy(a, b, c) { return p.invoke(func, this, slice(arguments)); }; break; case 4: p = function proxy(a, b, c, d) { return p.invoke(func, this, slice(arguments)); }; break; case 5: p = function proxy(a, b, c, d, e) { return p.invoke(func, this, slice(arguments)); }; break; case 6: p = function proxy(a, b, c, d, e, f) { return p.invoke(func, this, slice(arguments)); }; break; case 7: p = function proxy(a, b, c, d, e, f, g) { return p.invoke(func, this, slice(arguments)); }; break; case 8: p = function proxy(a, b, c, d, e, f, g, h) { return p.invoke(func, this, slice(arguments)); }; break; case 9: p = function proxy(a, b, c, d, e, f, g, h, i) { return p.invoke(func, this, slice(arguments)); }; break; case 10: p = function proxy(a, b, c, d, e, f, g, h, i, j) { return p.invoke(func, this, slice(arguments)); }; break; case 11: p = function proxy(a, b, c, d, e, f, g, h, i, j, k) { return p.invoke(func, this, slice(arguments)); }; break; case 12: p = function proxy(a, b, c, d, e, f, g, h, i, j, k, l) { return p.invoke(func, this, slice(arguments)); }; break; default: p = function proxy() { return p.invoke(func, this, slice(arguments)); }; break; /*eslint-enable*/ } const nameDescriptor = Object.getOwnPropertyDescriptor( originalFunc, "name", ); if (nameDescriptor && nameDescriptor.configurable) { // IE 11 functions don't have a name. // Safari 9 has names that are not configurable. Object.defineProperty(p, "name", nameDescriptor); } extend.nonEnum(p, { isSinonProxy: true, called: false, notCalled: true, calledOnce: false, calledTwice: false, calledThrice: false, callCount: 0, firstCall: null, firstArg: null, secondCall: null, thirdCall: null, lastCall: null, lastArg: null, args: [], returnValues: [], thisValues: [], exceptions: [], callIds: [], errorsWithCallStack: [], }); return p; } module.exports = createProxy;