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

Open
#2,663 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
45/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Stale
Tech stack
javascript
Domain
testing-qa

Research direction

Start by running the JavaScript reproduction in the issue with Sinon 17.0.1 and inspect the mock expectation path for callsFake() across multiple onCall() calls. The fix is complete when each fake returns its expected value, all three assertions pass, mock.verify() succeeds, and the accumulator remains empty.

Written by the indexing model from the issue text.

Description

Bug Unverified

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

	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.

Dominant language
JavaScript
Stars
9.8k
Forks
809
PR merge metrics
No merged PRs in 30d

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from sinonjs/sinon

All issues in sinonjs/sinon

Similar issues

More JavaScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.