Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 37 additions & 42 deletions src/sinon/default-behaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,35 @@ const { slice } = prototypes.array;
const useLeftMostCallback = -1;
const useRightMostCallback = -2;

/**
* Resets all mutually exclusive "return behavior" flags so that the most
* recently called behavior setter always takes effect ("last call wins").
*
* Without this, flags like `returnArgAt` (set by `returnsArg()`) can silently
* persist and override a later `.returns()` call because `returnArgAt` is
* checked first in behavior.invoke().
*
* @param {object} fake - the stub behavior object
*/
function resetReturnBehavior(fake) {
fake.returnArgAt = undefined;
fake.returnThis = false;
fake.throwArgAt = undefined;
fake.fakeFn = undefined;
fake.returnValue = undefined;
fake.returnValueDefined = false;
fake.resolve = false;
fake.reject = false;
fake.resolveArgAt = undefined;
fake.resolveThis = false;
fake.exception = undefined;
fake.exceptionCreator = undefined;
fake.callsThrough = false;
fake.callsThroughWithNew = false;
}

function throwsException(fake, error, message) {
resetReturnBehavior(fake);
if (typeof error === "function") {
fake.exceptionCreator = error;
} else if (typeof error === "string") {
Expand All @@ -32,10 +60,8 @@ function throwsException(fake, error, message) {

const defaultBehaviors = {
callsFake: function callsFake(fake, fn) {
resetReturnBehavior(fake);
fake.fakeFn = fn;
fake.exception = undefined;
fake.exceptionCreator = undefined;
fake.callsThrough = false;
},

callsArg: function callsArg(fake, index) {
Expand Down Expand Up @@ -143,65 +169,46 @@ const defaultBehaviors = {
throwsException: throwsException,

returns: function returns(fake, value) {
fake.callsThrough = false;
resetReturnBehavior(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");
}
fake.callsThrough = false;

resetReturnBehavior(fake);
fake.returnArgAt = index;
},

throwsArg: function throwsArg(fake, index) {
if (typeof index !== "number") {
throw new TypeError("argument index is not number");
}
fake.callsThrough = false;

resetReturnBehavior(fake);
fake.throwArgAt = index;
},

returnsThis: function returnsThis(fake) {
resetReturnBehavior(fake);
fake.returnThis = true;
fake.callsThrough = false;
},

resolves: function resolves(fake, value) {
resetReturnBehavior(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;
fake.callsThrough = false;
},

resolvesArg: function resolvesArg(fake, index) {
if (typeof index !== "number") {
throw new TypeError("argument index is not number");
}
resetReturnBehavior(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;
fake.callsThrough = false;
},

rejects: function rejects(fake, error, message) {
Expand All @@ -214,29 +221,17 @@ const defaultBehaviors = {
} else {
reason = error;
}
resetReturnBehavior(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;
fake.callsThrough = false;

return fake;
},

resolvesThis: function resolvesThis(fake) {
fake.returnValue = undefined;
fake.resolve = false;
resetReturnBehavior(fake);
fake.resolveThis = true;
fake.reject = false;
fake.returnValueDefined = false;
fake.exception = undefined;
fake.exceptionCreator = undefined;
fake.fakeFn = undefined;
fake.callsThrough = false;
},

callThrough: function callThrough(fake) {
Expand Down
56 changes: 56 additions & 0 deletions test/src/stub-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,32 @@ describe("stub", function () {
refute(fakeFn.called);
});

it("supersedes previous returnsArg", function () {
const stub = createStub();
stub.returnsArg(0);
stub.returns(42);

assert.equals(stub(99), 42);
});

it("supersedes previous returnsThis", function () {
const stub = createStub();
stub.returnsThis();
stub.returns(42);

assert.equals(stub.call({ x: 1 }), 42);
});

it("supersedes previous throwsArg", function () {
const stub = createStub();
stub.throwsArg(0);
stub.returns(42);

refute.exception(function () {
assert.equals(stub(new Error("should not throw")), 42);
});
});

it("supersedes previous callThrough", function () {
const obj = {
fn() {
Expand Down Expand Up @@ -819,6 +845,26 @@ describe("stub", function () {
);
});

it("supersedes previous returnsArg", function () {
const stub = createStub();
stub.returnsArg(0);
stub.throwsArg(0);

assert.exception(function () {
stub(new Error("expected"));
});
});

it("supersedes previous returnsThis", function () {
const stub = createStub();
stub.returnsThis();
stub.throwsArg(0);

assert.exception(function () {
stub(new Error("expected"));
});
});

it("should be reset by .resetBehavior", function () {
const stub = createStub();

Expand Down Expand Up @@ -929,6 +975,16 @@ describe("stub", function () {

assert.same(obj.fn(), obj);
});

it("supersedes previous returnsArg", function () {
const instance = {};
instance.stub = createStub();
instance.stub.returnsArg(0);
instance.stub.returnsThis();

assert.same(instance.stub("ignored"), instance);
});

});

describe(".throws", function () {
Expand Down
Loading