UNPKG

sinon

Version:

JavaScript test spies, stubs and mocks.

193 lines (160 loc) 6.03 kB
"use strict"; var arrayProto = require("@sinonjs/commons").prototypes.array; var createProxy = require("./proxy"); var extend = require("./util/core/extend"); var functionName = require("@sinonjs/commons").functionName; var getPropertyDescriptor = require("./util/core/get-property-descriptor"); var deepEqual = require("@sinonjs/samsam").deepEqual; var isEsModule = require("./util/core/is-es-module"); var proxyCallUtil = require("./proxy-call-util"); var walkObject = require("./util/core/walk-object"); var wrapMethod = require("./util/core/wrap-method"); var sinonFormat = require("./util/core/format"); var valueToString = require("@sinonjs/commons").valueToString; /* cache references to library methods so that they also can be stubbed without problems */ var forEach = arrayProto.forEach; var pop = arrayProto.pop; var push = arrayProto.push; var slice = arrayProto.slice; var filter = Array.prototype.filter; var uuid = 0; function matches(fake, args, strict) { var 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 var spyApi = { formatters: require("./spy-formatters"), withArgs: function() { var args = slice(arguments); var matching = pop(this.matchingFakes(args, true)); if (matching) { return matching; } var original = this; var 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); }); }, printf: function(format) { var spyInstance = this; var args = slice(arguments, 1); var formatter; return (format || "").replace(/%(.)/g, function(match, specifyer) { formatter = spyApi.formatters[specifyer]; if (typeof formatter === "function") { return String(formatter(spyInstance, args)); } else if (!isNaN(parseInt(specifyer, 10))) { return sinonFormat(args[specifyer - 1]); } return "%" + specifyer; }); } }; /* eslint-disable local-rules/no-prototype-methods */ var 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." ); }); /* eslint-enable local-rules/no-prototype-methods */ function createSpy(func) { var name; var funk = func; if (typeof funk !== "function") { funk = function() { return; }; } else { name = functionName(funk); } var 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) { var descriptor, methodDesc; 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])); } descriptor = {}; methodDesc = getPropertyDescriptor(object, property); forEach(types, function(type) { descriptor[type] = createSpy(methodDesc[type]); }); return wrapMethod(object, property, descriptor); } extend(spy, spyApi); module.exports = spy;