sinon
Version:
JavaScript test spies, stubs and mocks.
193 lines (166 loc) • 5.57 kB
JavaScript
const arrayProto = require("@sinonjs/commons").prototypes.array;
const createProxy = require("./proxy");
const extend = require("./util/core/extend");
const functionName = require("@sinonjs/commons").functionName;
const getPropertyDescriptor = require("./util/core/get-property-descriptor");
const deepEqual = require("@sinonjs/samsam").deepEqual;
const isEsModule = require("./util/core/is-es-module");
const proxyCallUtil = require("./proxy-call-util");
const walkObject = require("./util/core/walk-object");
const wrapMethod = require("./util/core/wrap-method");
const valueToString = require("@sinonjs/commons").valueToString;
/* cache references to library methods so that they also can be stubbed without problems */
const forEach = arrayProto.forEach;
const pop = arrayProto.pop;
const push = arrayProto.push;
const slice = arrayProto.slice;
const filter = Array.prototype.filter;
let uuid = 0;
function matches(fake, args, strict) {
const margs = fake.matchingArguments;
if (
margs.length <= args.length &&
deepEqual(slice(args, 0, margs.length), margs)
) {
return !strict || margs.length === args.length;
}
return false;
}
// Public API
const spyApi = {
withArgs: function () {
const args = slice(arguments);
const matching = pop(this.matchingFakes(args, true));
if (matching) {
return matching;
}
const original = this;
const fake = this.instantiateFake();
fake.matchingArguments = args;
fake.parent = this;
push(this.fakes, fake);
fake.withArgs = function () {
return original.withArgs.apply(original, arguments);
};
forEach(original.args, function (arg, i) {
if (!matches(fake, arg)) {
return;
}
proxyCallUtil.incrementCallCount(fake);
push(fake.thisValues, original.thisValues[i]);
push(fake.args, arg);
push(fake.returnValues, original.returnValues[i]);
push(fake.exceptions, original.exceptions[i]);
push(fake.callIds, original.callIds[i]);
});
proxyCallUtil.createCallProperties(fake);
return fake;
},
// Override proxy default implementation
matchingFakes: function (args, strict) {
return filter.call(this.fakes, function (fake) {
return matches(fake, args, strict);
});
},
};
/* eslint-disable @sinonjs/no-prototype-methods/no-prototype-methods */
const delegateToCalls = proxyCallUtil.delegateToCalls;
delegateToCalls(spyApi, "callArg", false, "callArgWith", true, function () {
throw new Error(
`${this.toString()} cannot call arg since it was not yet invoked.`,
);
});
spyApi.callArgWith = spyApi.callArg;
delegateToCalls(spyApi, "callArgOn", false, "callArgOnWith", true, function () {
throw new Error(
`${this.toString()} cannot call arg since it was not yet invoked.`,
);
});
spyApi.callArgOnWith = spyApi.callArgOn;
delegateToCalls(spyApi, "throwArg", false, "throwArg", false, function () {
throw new Error(
`${this.toString()} cannot throw arg since it was not yet invoked.`,
);
});
delegateToCalls(spyApi, "yield", false, "yield", true, function () {
throw new Error(
`${this.toString()} cannot yield since it was not yet invoked.`,
);
});
// "invokeCallback" is an alias for "yield" since "yield" is invalid in strict mode.
spyApi.invokeCallback = spyApi.yield;
delegateToCalls(spyApi, "yieldOn", false, "yieldOn", true, function () {
throw new Error(
`${this.toString()} cannot yield since it was not yet invoked.`,
);
});
delegateToCalls(spyApi, "yieldTo", false, "yieldTo", true, function (property) {
throw new Error(
`${this.toString()} cannot yield to '${valueToString(
property,
)}' since it was not yet invoked.`,
);
});
delegateToCalls(
spyApi,
"yieldToOn",
false,
"yieldToOn",
true,
function (property) {
throw new Error(
`${this.toString()} cannot yield to '${valueToString(
property,
)}' since it was not yet invoked.`,
);
},
);
function createSpy(func) {
let name;
let funk = func;
if (typeof funk !== "function") {
funk = function () {
return;
};
} else {
name = functionName(funk);
}
const proxy = createProxy(funk, funk);
// Inherit spy API:
extend.nonEnum(proxy, spyApi);
extend.nonEnum(proxy, {
displayName: name || "spy",
fakes: [],
instantiateFake: createSpy,
id: `spy#${uuid++}`,
});
return proxy;
}
function spy(object, property, types) {
if (isEsModule(object)) {
throw new TypeError("ES Modules cannot be spied");
}
if (!property && typeof object === "function") {
return createSpy(object);
}
if (!property && typeof object === "object") {
return walkObject(spy, object);
}
if (!object && !property) {
return createSpy(function () {
return;
});
}
if (!types) {
return wrapMethod(object, property, createSpy(object[property]));
}
const descriptor = {};
const methodDesc = getPropertyDescriptor(object, property);
forEach(types, function (type) {
descriptor[type] = createSpy(methodDesc[type]);
});
return wrapMethod(object, property, descriptor);
}
extend(spy, spyApi);
module.exports = spy;
;