diff --git a/src/sinon/mock.js b/src/sinon/mock.js index 9f36b141b..2e34bae50 100644 --- a/src/sinon/mock.js +++ b/src/sinon/mock.js @@ -7,7 +7,7 @@ import wrapMethod from "./util/core/wrap-method.js"; const { prototypes } = commons; const { deepEqual } = samsam; -const { concat, filter, forEach, every, join, push, slice, unshift } = +const { concat, filter, forEach, every, join, push, reduce, slice, unshift } = prototypes.array; /** @@ -125,6 +125,13 @@ extend(mock, { ? this.expectations[method] : []; const currentArgs = args || []; + const callIndex = reduce( + expectations, + function (count, expectation) { + return count + expectation.callCount; + }, + 0, + ); let available; const expectationsWithMatchingArgs = filter( @@ -151,7 +158,12 @@ extend(mock, { ); if (expectationsToApply.length > 0) { - return expectationsToApply[0].apply(thisValue, args); + return invokeExpectation( + expectationsToApply[0], + thisValue, + args, + callIndex, + ); } const messages = []; @@ -166,7 +178,7 @@ extend(mock, { }); if (available && exhausted === 0) { - return available.apply(thisValue, args); + return invokeExpectation(available, thisValue, args, callIndex); } forEach(expectations, function (expectation) { @@ -202,3 +214,12 @@ extend(mock, { mockExpectation.fail(join(messages, "\n")); }, }); + +function invokeExpectation(expectation, thisValue, args, callIndex) { + expectation.behaviorCallIndex = callIndex; + try { + return expectation.apply(thisValue, args); + } finally { + delete expectation.behaviorCallIndex; + } +} diff --git a/src/sinon/stub.js b/src/sinon/stub.js index 4e84ed237..c3e78bcc7 100644 --- a/src/sinon/stub.js +++ b/src/sinon/stub.js @@ -173,7 +173,11 @@ function getDefaultBehavior(stubInstance) { } function getCurrentBehavior(stubInstance) { - const currentBehavior = stubInstance.behaviors[stubInstance.callCount - 1]; + const currentCall = + typeof stubInstance.behaviorCallIndex === "number" + ? stubInstance.behaviorCallIndex + : stubInstance.callCount - 1; + const currentBehavior = stubInstance.behaviors[currentCall]; return currentBehavior && currentBehavior.isPresent() ? currentBehavior : getDefaultBehavior(stubInstance); diff --git a/test/src/mock-test.js b/test/src/mock-test.js index 542eb58d8..60cd7626d 100644 --- a/test/src/mock-test.js +++ b/test/src/mock-test.js @@ -1234,6 +1234,51 @@ describe("sinonMock", function () { object.method(42); }); }); + + it("returns callsFake values from ordered matching expectations", function () { + const object = { + accumulator: [], + accumulate: function (thing) { + this.accumulator.push(thing); + return `added ${thing}`; + }, + }; + const mock = sinonMock(object); + + mock.expects("accumulate") + .withArgs("first thing") + .onCall(0) + .callsFake(function (thing) { + return `mock-added ${thing}`; + }); + mock.expects("accumulate") + .withArgs("second thing") + .onCall(1) + .callsFake(function (thing) { + return `mock-added ${thing}`; + }); + mock.expects("accumulate") + .withArgs("third thing") + .onCall(2) + .callsFake(function (thing) { + return `mock-added ${thing}`; + }); + + assert.equals(object.accumulator.length, 0); + assert.equals( + object.accumulate("first thing"), + "mock-added first thing", + ); + assert.equals( + object.accumulate("second thing"), + "mock-added second thing", + ); + assert.equals( + object.accumulate("third thing"), + "mock-added third thing", + ); + mock.verify(); + }); }); describe("mock function", function () {