diff --git a/lib/sinon/default-behaviors.js b/lib/sinon/default-behaviors.js index a2731da82..dff8c9d81 100644 --- a/lib/sinon/default-behaviors.js +++ b/lib/sinon/default-behaviors.js @@ -30,59 +30,90 @@ function throwsException(fake, error, message) { } } +const SKIP_OPTIONS_FOR_YIELDS = { + skipReturn: true, + skipThrows: true, +}; + +function clear(fake, options) { + fake.fakeFn = undefined; + + fake.callsThrough = undefined; + fake.callsThroughWithNew = undefined; + + if (!options || !options.skipThrows) { + fake.exception = undefined; + fake.exceptionCreator = undefined; + fake.throwArgAt = undefined; + } + + fake.callArgAt = undefined; + fake.callbackArguments = undefined; + fake.callbackContext = undefined; + fake.callArgProp = undefined; + fake.callbackAsync = undefined; + + if (!options || !options.skipReturn) { + fake.returnValue = undefined; + fake.returnValueDefined = undefined; + fake.returnArgAt = undefined; + fake.returnThis = undefined; + } + + fake.resolve = undefined; + fake.resolveThis = undefined; + fake.resolveArgAt = undefined; + + fake.reject = undefined; +} + const defaultBehaviors = { callsFake: function callsFake(fake, fn) { + clear(fake); + fake.fakeFn = fn; - fake.exception = undefined; - fake.exceptionCreator = undefined; }, callsArg: function callsArg(fake, index) { if (typeof index !== "number") { throw new TypeError("argument index is not number"); } + clear(fake); fake.callArgAt = index; fake.callbackArguments = []; - fake.callbackContext = undefined; - fake.callArgProp = undefined; - fake.callbackAsync = false; }, callsArgOn: function callsArgOn(fake, index, context) { if (typeof index !== "number") { throw new TypeError("argument index is not number"); } + clear(fake); fake.callArgAt = index; fake.callbackArguments = []; fake.callbackContext = context; - fake.callArgProp = undefined; - fake.callbackAsync = false; }, callsArgWith: function callsArgWith(fake, index) { if (typeof index !== "number") { throw new TypeError("argument index is not number"); } + clear(fake); fake.callArgAt = index; fake.callbackArguments = slice(arguments, 2); - fake.callbackContext = undefined; - fake.callArgProp = undefined; - fake.callbackAsync = false; }, callsArgOnWith: function callsArgWith(fake, index, context) { if (typeof index !== "number") { throw new TypeError("argument index is not number"); } + clear(fake); fake.callArgAt = index; fake.callbackArguments = slice(arguments, 3); fake.callbackContext = context; - fake.callArgProp = undefined; - fake.callbackAsync = false; }, usingPromise: function usingPromise(fake, promiseLibrary) { @@ -90,67 +121,59 @@ const defaultBehaviors = { }, yields: function (fake) { + clear(fake, SKIP_OPTIONS_FOR_YIELDS); + fake.callArgAt = useLeftMostCallback; fake.callbackArguments = slice(arguments, 1); - fake.callbackContext = undefined; - fake.callArgProp = undefined; - fake.callbackAsync = false; - fake.fakeFn = undefined; }, yieldsRight: function (fake) { + clear(fake, SKIP_OPTIONS_FOR_YIELDS); + fake.callArgAt = useRightMostCallback; fake.callbackArguments = slice(arguments, 1); - fake.callbackContext = undefined; - fake.callArgProp = undefined; - fake.callbackAsync = false; - fake.fakeFn = undefined; }, yieldsOn: function (fake, context) { + clear(fake, SKIP_OPTIONS_FOR_YIELDS); + fake.callArgAt = useLeftMostCallback; fake.callbackArguments = slice(arguments, 2); fake.callbackContext = context; - fake.callArgProp = undefined; - fake.callbackAsync = false; - fake.fakeFn = undefined; }, yieldsTo: function (fake, prop) { + clear(fake, SKIP_OPTIONS_FOR_YIELDS); + fake.callArgAt = useLeftMostCallback; fake.callbackArguments = slice(arguments, 2); - fake.callbackContext = undefined; fake.callArgProp = prop; - fake.callbackAsync = false; - fake.fakeFn = undefined; }, yieldsToOn: function (fake, prop, context) { + clear(fake, SKIP_OPTIONS_FOR_YIELDS); + fake.callArgAt = useLeftMostCallback; fake.callbackArguments = slice(arguments, 3); fake.callbackContext = context; fake.callArgProp = prop; - fake.callbackAsync = false; - fake.fakeFn = undefined; }, throws: throwsException, throwsException: throwsException, returns: function returns(fake, value) { + clear(fake); + fake.returnValue = value; - fake.resolve = false; - fake.reject = false; fake.returnValueDefined = true; - fake.exception = undefined; - fake.exceptionCreator = undefined; - fake.fakeFn = undefined; }, returnsArg: function returnsArg(fake, index) { if (typeof index !== "number") { throw new TypeError("argument index is not number"); } + clear(fake); fake.returnArgAt = index; }, @@ -159,38 +182,33 @@ const defaultBehaviors = { if (typeof index !== "number") { throw new TypeError("argument index is not number"); } + clear(fake); fake.throwArgAt = index; }, returnsThis: function returnsThis(fake) { + clear(fake); + fake.returnThis = true; }, resolves: function resolves(fake, value) { + clear(fake); + fake.returnValue = value; fake.resolve = true; - fake.resolveThis = false; - fake.reject = false; fake.returnValueDefined = true; - fake.exception = undefined; - fake.exceptionCreator = undefined; - fake.fakeFn = undefined; }, resolvesArg: function resolvesArg(fake, index) { if (typeof index !== "number") { throw new TypeError("argument index is not number"); } + clear(fake); + fake.resolveArgAt = index; - fake.returnValue = undefined; fake.resolve = true; - fake.resolveThis = false; - fake.reject = false; - fake.returnValueDefined = false; - fake.exception = undefined; - fake.exceptionCreator = undefined; - fake.fakeFn = undefined; }, rejects: function rejects(fake, error, message) { @@ -203,34 +221,30 @@ const defaultBehaviors = { } else { reason = error; } + clear(fake); + fake.returnValue = reason; - fake.resolve = false; - fake.resolveThis = false; fake.reject = true; fake.returnValueDefined = true; - fake.exception = undefined; - fake.exceptionCreator = undefined; - fake.fakeFn = undefined; return fake; }, resolvesThis: function resolvesThis(fake) { - fake.returnValue = undefined; - fake.resolve = false; + clear(fake); + fake.resolveThis = true; - fake.reject = false; - fake.returnValueDefined = false; - fake.exception = undefined; - fake.exceptionCreator = undefined; - fake.fakeFn = undefined; }, callThrough: function callThrough(fake) { + clear(fake); + fake.callsThrough = true; }, callThroughWithNew: function callThroughWithNew(fake) { + clear(fake); + fake.callsThroughWithNew = true; }, diff --git a/test/stub-test.js b/test/stub-test.js index 68f44271d..957cce9aa 100644 --- a/test/stub-test.js +++ b/test/stub-test.js @@ -225,6 +225,17 @@ describe("stub", function () { refute(fakeFn.called); }); + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return 1; + }, + }; + const stub = createStub(obj, "fn").callThrough().returns(2); + + assert.equals(stub(), 2); + }); + it("throws only on the first call", function () { const stub = createStub(); stub.returns("no exception"); @@ -284,6 +295,19 @@ describe("stub", function () { return stub().then(); }); + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return Promise.resolve(1); + }, + }; + const stub = createStub(obj, "fn").callThrough().resolves(2); + + return stub().then(function (actual) { + assert.equals(actual, 2); + }); + }); + it("supersedes previous callsFake", function () { const fakeFn = createStub().resolves(2); const stub = createStub().callsFake(fakeFn).resolves(1); @@ -405,6 +429,24 @@ describe("stub", function () { }); }); + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return Promise.resolve(1); + }, + }; + const reason = new Error(); + const stub = createStub(obj, "fn").callThrough().rejects(reason); + + return stub() + .then(function () { + referee.fail("this should not resolve"); + }) + .catch(function (actual) { + assert.same(actual, reason); + }); + }); + it("does not invoke Promise.reject when the behavior is added to the stub", function () { const rejectSpy = createSpy(Promise, "reject"); const stub = createStub(); @@ -481,6 +523,19 @@ describe("stub", function () { }); }); + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return Promise.resolve(1); + }, + }; + createStub(obj, "fn").callThrough().resolvesThis(); + + return obj.fn().then(function (actual) { + assert.same(actual, obj); + }); + }); + it("supersedes previous callsFake", function () { const fakeInstance = {}; const fakeFn = createStub().resolves(fakeInstance); @@ -563,6 +618,19 @@ describe("stub", function () { }); }); + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return Promise.resolve("nooooo"); + }, + }; + createStub(obj, "fn").callThrough().resolvesArg(1); + + return obj.fn("zero", "one").then(function (actual) { + assert.same(actual, "one"); + }); + }); + it("does not invoke Promise.resolve when the behavior is added to the stub", function () { const resolveSpy = createSpy(Promise, "resolve"); const stub = createStub(); @@ -622,6 +690,17 @@ describe("stub", function () { refute(fakeFn.called); }); + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return "nooooo"; + }, + }; + createStub(obj, "fn").callThrough().returnsArg(0); + + assert.equals(obj.fn("myarg"), "myarg"); + }); + it("throws if no index is specified", function () { const stub = createStub(); @@ -747,6 +826,25 @@ describe("stub", function () { refute(fakeFn.called); }); + + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return "nooooo"; + }, + }; + createStub(obj, "fn").callThrough().throwsArg(1); + const expectedError = new Error("catpants"); + + assert.exception( + function () { + obj.fn(null, expectedError); + }, + function (error) { + return error.message === expectedError.message; + }, + ); + }); }); describe(".returnsThis", function () { @@ -800,6 +898,17 @@ describe("stub", function () { assert.same(instance.stub(), instance); refute(fakeFn.called); }); + + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return "nooooo"; + }, + }; + createStub(obj, "fn").callThrough().returnsThis(); + + assert.same(obj.fn(), obj); + }); }); describe(".usingPromise", function () { @@ -1034,6 +1143,20 @@ describe("stub", function () { }); refute(fakeFn.called); }); + + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return "nooooo"; + }, + }; + const expectedError = new Error("catpants"); + createStub(obj, "fn").callThrough().throws(expectedError); + + assert.exception(obj.fn, { + message: expectedError.message, + }); + }); }); describe(".callsArg", function () { @@ -1110,6 +1233,20 @@ describe("stub", function () { assert.same(stub(callback), "return value"); assert(callback.calledOnce); }); + + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return "nooooo"; + }, + }; + const stub = createStub(obj, "fn").callThrough().callsArg(0); + + const callback = createStub().returns("return value"); + + assert.same(stub(callback), "return value"); + assert(callback.calledOnce); + }); }); describe(".callsArgWith", function () { @@ -1182,6 +1319,21 @@ describe("stub", function () { assert.same(stub(callback), "return value"); assert(callback.calledOnce); }); + + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return "nooooo"; + }, + }; + const stub = createStub(obj, "fn") + .callThrough() + .callsArgWith(0, "test"); + const callback = createStub().returns("return value"); + + assert.same(stub(callback), "return value"); + assert(callback.calledOnce); + }); }); describe(".callsArgOn", function () { @@ -1269,6 +1421,20 @@ describe("stub", function () { assert(callback.calledOnce); assert(callback.calledOn(this.fakeContext)); }); + + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return "nooooo"; + }, + }; + createStub(obj, "fn").callThrough().callsArgOn(0, this.fakeContext); + const callback = createStub().returns("return value"); + + assert.same(obj.fn(callback), "return value"); + assert(callback.calledOnce); + assert(callback.calledOn(this.fakeContext)); + }); }); describe(".callsArgOnWith", function () { @@ -1391,6 +1557,25 @@ describe("stub", function () { assert(callback.calledOnce); assert(callback.calledWith(object)); }); + + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return "nooooo"; + }, + }; + + const arg = "hello"; + createStub(obj, "fn") + .callThrough() + .callsArgOnWith(1, this.fakeContext, arg); + const callback = createStub(); + + obj.fn(1, callback); + + assert(callback.calledWith(arg)); + assert(callback.calledOn(this.fakeContext)); + }); }); describe(".callsFake", function () { @@ -1444,6 +1629,20 @@ describe("stub", function () { assert(fakeFn.called); assert(returned === 5); }); + + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return "nooooo"; + }, + }; + + createStub(obj, "fn") + .callThrough() + .callsFake(() => "yeees"); + + assert.same(obj.fn(), "yeees"); + }); }); describe(".objectMethod", function () { @@ -1740,6 +1939,20 @@ describe("stub", function () { assert(spy.calledWith, 2); refute(fakeFn.called); }); + + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return 1; + }, + }; + + createStub(obj, "fn").callThrough().yields(2); + const spy = createSpy(); + obj.fn(spy); + + assert(spy.calledWith, 2); + }); }); describe(".yieldsRight", function () { @@ -1870,6 +2083,20 @@ describe("stub", function () { assert(spy.calledWith, 2); refute(fakeFn.called); }); + + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return 1; + }, + }; + + createStub(obj, "fn").callThrough().yieldsRight(2); + const spy = createSpy(); + obj.fn(spy); + + assert(spy.calledWith, 2); + }); }); describe(".yieldsOn", function () { @@ -2027,6 +2254,20 @@ describe("stub", function () { assert(spy.calledWith, 2); refute(fakeFn.called); }); + + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return 1; + }, + }; + + createStub(obj, "fn").callThrough().yieldsOn(this.fakeContext, 2); + const spy = createSpy(); + obj.fn(spy); + + assert(spy.calledWith, 2); + }); }); describe(".yieldsTo", function () { @@ -2178,6 +2419,20 @@ describe("stub", function () { assert(callback.calledWith(2)); refute(fakeFn.called); }); + + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return 1; + }, + }; + + createStub(obj, "fn").callThrough().yieldsTo("success", 2); + const callback = createSpy(); + obj.fn({ success: callback }); + + assert(callback.calledWith, 2); + }); }); describe(".yieldsToOn", function () { @@ -2364,6 +2619,22 @@ describe("stub", function () { assert(callback.calledWith(2)); refute(fakeFn.called); }); + + it("supersedes previous callThrough", function () { + const obj = { + fn() { + return 1; + }, + }; + + createStub(obj, "fn") + .callThrough() + .yieldsToOn("success", this.fakeContext, 2); + const callback = createSpy(); + obj.fn({ success: callback }); + + assert(callback.calledWith(2)); + }); }); describe(".withArgs", function () {