UNPKG

sinon

Version:

JavaScript test spies, stubs and mocks.

244 lines (202 loc) 7.61 kB
"use strict"; var arrayProto = require("@sinonjs/commons").prototypes.array; var extend = require("./util/core/extend"); var functionName = require("@sinonjs/commons").functionName; var nextTick = require("./util/core/next-tick"); var valueToString = require("@sinonjs/commons").valueToString; var concat = arrayProto.concat; var forEach = arrayProto.forEach; var join = arrayProto.join; var reverse = arrayProto.reverse; var slice = arrayProto.slice; var useLeftMostCallback = -1; var useRightMostCallback = -2; function getCallback(behavior, args) { var callArgAt = behavior.callArgAt; if (callArgAt >= 0) { return args[callArgAt]; } var argumentList; if (callArgAt === useLeftMostCallback) { argumentList = args; } if (callArgAt === useRightMostCallback) { argumentList = reverse(slice(args)); } var callArgProp = behavior.callArgProp; for (var i = 0, l = argumentList.length; i < l; ++i) { if (!callArgProp && typeof argumentList[i] === "function") { return argumentList[i]; } if (callArgProp && argumentList[i] && typeof argumentList[i][callArgProp] === "function") { return argumentList[i][callArgProp]; } } return null; } function getCallbackError(behavior, func, args) { if (behavior.callArgAt < 0) { var msg; if (behavior.callArgProp) { msg = functionName(behavior.stub) + " expected to yield to '" + valueToString(behavior.callArgProp) + "', but no object with such a property was passed."; } else { msg = functionName(behavior.stub) + " expected to yield, but no callback was passed."; } if (args.length > 0) { msg += " Received [" + join(args, ", ") + "]"; } return msg; } return "argument at index " + behavior.callArgAt + " is not a function: " + func; } function ensureArgs(name, behavior, args) { // map function name to internal property // callsArg => callArgAt var property = name.replace(/sArg/, "ArgAt"); var index = behavior[property]; if (index >= args.length) { throw new TypeError( name + " failed: " + (index + 1) + " arguments required but only " + args.length + " present" ); } } function callCallback(behavior, args) { if (typeof behavior.callArgAt === "number") { ensureArgs("callsArg", behavior, args); var func = getCallback(behavior, args); if (typeof func !== "function") { throw new TypeError(getCallbackError(behavior, func, args)); } if (behavior.callbackAsync) { nextTick(function () { func.apply(behavior.callbackContext, behavior.callbackArguments); }); } else { return func.apply(behavior.callbackContext, behavior.callbackArguments); } } return undefined; } var proto = { create: function create(stub) { var behavior = extend({}, proto); delete behavior.create; delete behavior.addBehavior; delete behavior.createBehavior; behavior.stub = stub; if (stub.defaultBehavior && stub.defaultBehavior.promiseLibrary) { behavior.promiseLibrary = stub.defaultBehavior.promiseLibrary; } return behavior; }, isPresent: function isPresent() { return (typeof this.callArgAt === "number" || this.exception || this.exceptionCreator || typeof this.returnArgAt === "number" || this.returnThis || typeof this.resolveArgAt === "number" || this.resolveThis || typeof this.throwArgAt === "number" || this.fakeFn || this.returnValueDefined); }, invoke: function invoke(context, args) { /* * callCallback (conditionally) calls ensureArgs * * Note: callCallback intentionally happens before * everything else and cannot be moved lower */ var returnValue = callCallback(this, args); if (this.exception) { throw this.exception; } else if (this.exceptionCreator) { this.exception = this.exceptionCreator(); this.exceptionCreator = undefined; throw this.exception; } else if (typeof this.returnArgAt === "number") { ensureArgs("returnsArg", this, args); return args[this.returnArgAt]; } else if (this.returnThis) { return context; } else if (typeof this.throwArgAt === "number") { ensureArgs("throwsArg", this, args); throw args[this.throwArgAt]; } else if (this.fakeFn) { return this.fakeFn.apply(context, args); } else if (typeof this.resolveArgAt === "number") { ensureArgs("resolvesArg", this, args); return (this.promiseLibrary || Promise).resolve(args[this.resolveArgAt]); } else if (this.resolveThis) { return (this.promiseLibrary || Promise).resolve(context); } else if (this.resolve) { return (this.promiseLibrary || Promise).resolve(this.returnValue); } else if (this.reject) { return (this.promiseLibrary || Promise).reject(this.returnValue); } else if (this.callsThrough) { return this.stub.wrappedMethod.apply(context, args); } else if (typeof this.returnValue !== "undefined") { return this.returnValue; } else if (typeof this.callArgAt === "number") { return returnValue; } return this.returnValue; }, onCall: function onCall(index) { return this.stub.onCall(index); }, onFirstCall: function onFirstCall() { return this.stub.onFirstCall(); }, onSecondCall: function onSecondCall() { return this.stub.onSecondCall(); }, onThirdCall: function onThirdCall() { return this.stub.onThirdCall(); }, withArgs: function withArgs(/* arguments */) { throw new Error( "Defining a stub by invoking \"stub.onCall(...).withArgs(...)\" " + "is not supported. Use \"stub.withArgs(...).onCall(...)\" " + "to define sequential behavior for calls with certain arguments." ); } }; function createAsyncVersion(syncFnName) { return function () { var result = this[syncFnName].apply(this, arguments); this.callbackAsync = true; return result; }; } // create asynchronous versions of callsArg* and yields* methods forEach(Object.keys(proto), function (method) { // need to avoid creating anotherasync versions of the newly added async methods if (method.match(/^(callsArg|yields)/) && !method.match(/Async/)) { proto[method + "Async"] = createAsyncVersion(method); } }); function createBehavior(behaviorMethod) { return function () { this.defaultBehavior = this.defaultBehavior || proto.create(this); this.defaultBehavior[behaviorMethod].apply(this.defaultBehavior, arguments); return this; }; } function addBehavior(stub, name, fn) { proto[name] = function () { fn.apply(this, concat([this], slice(arguments))); return this.stub || this; }; stub[name] = createBehavior(name); } proto.addBehavior = addBehavior; proto.createBehavior = createBehavior; module.exports = proto;