#2663·sinon

callsFake() on mocks only returns a value the first time

Author: Anonymous-CowardCreated Sep 13, 2025Updated Sep 13, 2025
LabelsBugUnverified

Describe the bug

If multiple distinct expectations, for multiple calls to the same mock, are set, and callsFake() is used on each of them, while the fake functions do get called, the return value is discarded for all of them but the first one.

To Reproduce

javascript
	it("Does not return all values from fake functions for mocks", function() {
		obj = {
			accumulator: [],
			accumulate: function(thing) {
				this.accumulator.push(thing);
				return `added ${thing}`;
			}
		}

		mock = sinon.mock(obj);

		mock.expects("accumulate")
			.withArgs("first thing").onCall(0).callsFake(thing => "mock-added first thing"); // .returns("mock-added first thing");
		mock.expects("accumulate")
			.withArgs("second thing").onCall(1).callsFake(thing => "mock-added second thing"); // .returns("mock-added second thing");
		mock.expects("accumulate")
			.withArgs("third thing").onCall(2).callsFake(thing => "mock-added third thing"); // .returns("mock-added third thing");

		expect(obj.accumulator).to.have.lengthOf(0);

		expect(obj.accumulate("first thing")).to.equal("mock-added first thing");
		expect(obj.accumulate("second thing")).to.equal("mock-added second thing");
		expect(obj.accumulate("third thing")).to.equal("mock-added third thing");

		mock.verify();
	});

Expected behavior

All expectations in the three lines before mock.verify() that verify calls to obj.accumulate() should pass, while obj.accumulator should stay empty.

Actual behavior

obj.accumulator does stay empty, because the first expect(...) statement passes. But the test fails in the second expect(...).to.equal(...) line, with this error message:

AssertionError: expected undefined to equal 'mock-added second thing'

If the commented out .returns(...) fragments are enabled, the test passes.

What's odd and inconsistent, however, is that if only the last two .returns(...) fragments are enabled, while the first one is left commented out, the test still passes. So the return value of the first callsFake(...) is returned, but the return values of subsequent calls to fake functions installed by callsFake() are discarded, and require an explicit additional returns(...) for a return value to be returned from the mocked call.

Screenshots

n.a.

Context (please complete the following information):

  • Sinon version : 17.0.1
  • Runtime: Node v22.19.0
    • Output of npx envinfo --browsers --binaries:
  Binaries:
    Node: 22.19.0 - /usr/bin/node
    npm: 10.9.2 - ~/KBV/src/kbv-devops/certs-updater/node_modules/.bin/npm
  Browsers:
    Chrome: 140.0.7339.127
  • Other relevant environmental info:
  • Other libraries you are using: Chai 4.5.0, mocha 11.0.1
  • Example URL: n.a.

Additional context

n.a.