Sinon: `calledWith` reporting a match with completely different arguments (promises)

Created on 5 Mar 2021  路  8Comments  路  Source: sinonjs/sinon

Describe the bug
Sinon spy calledWith doesn't work with promises

To Reproduce
Steps to reproduce the behavior:

const mySpy = sinon.spy();
mySpy(Promise.resolve("a"));
const output = mySpy.calledWith(Promise.resolve("b"));
console.log(output); // true when it should be false

Expected behavior
calledWith should fail when comparing obviously different arguments than the one it was previously called with.
I guess the deepEqual function used inside this method is not correctly comparing promises.
The example above should be able to fail for either of these 2 options:

  1. The promise instance provided as an argument of the spy call and the calledWith expectation are different
  2. The promises resolve to a different value

I guess option 1 would be the expected behaviour by most users and it would probably be the easiest to implement.

Context (please complete the following information):

  • Library version: 9.2.4
  • Environment: node v14.15.1
  • Other libraries you are using: can be replicated just with sinon
Bug

Most helpful comment

I agree that your example should return false since the promise instances are different. This is a bug.

However, I think we can only do a strict equality check on promises since it's impossible to inspect the state and value of a promise synchronously.

To write an assertion on the resolved promise value, you'd depend on your assertion library to support that, e.g. with referee I would write it like this:

await assert.resolves(mySpy.lastCall.args[0], 'b');

All 8 comments

I agree that your example should return false since the promise instances are different. This is a bug.

However, I think we can only do a strict equality check on promises since it's impossible to inspect the state and value of a promise synchronously.

To write an assertion on the resolved promise value, you'd depend on your assertion library to support that, e.g. with referee I would write it like this:

await assert.resolves(mySpy.lastCall.args[0], 'b');

I am personally using chai so what I am currently doing right now is pretty much the same:

expect(await mySpy.getCall(0).args[0]).to.deel.equal('a');

It looks really ugly though :(

But sometimes I have the specific Promise instance right on the test so that would look like

expect(mySpy.calledWith(myPromise)).to.equal(true)

or even prettier with sinonChai

expect(mySpy).to.have.been.calledWith(myPromise);

This will need to be fixed in @sinonjs/samsam (see https://github.com/sinonjs/samsam/issues/216). @NoxWings Would you like to look into it yourself?

I've uploaded a PR. Just let me know if you need anything else there.

I wonder why calledWith does not check for strict equality (===). I am using this type of assertions as a workaround:

const mySpy = sinon.spy();

const anObject = {};
const anotherObject = {};

mySpy(anObject);
const output = mySpy.calledWith(anotherObject);
console.log(output); // true, I expect it to be false

// My workaround with chai

expect(mySpy.calledOnce).to.be.true;
expect(mySpy.firstCall.args[0]).to.be.eq(anObject);

if calledWith checked for strict equality, I would just say:

expect(mySpy.calledWith(anObject)).to.be.true;

This becomes even harder when there are multiple object arguments.

@srknzl You can solve that with expect(mySpy.calledWith(sinon.match.same(anObject))).

Thanks a lot, I checked matchers but looks like I missed that one

PR is pending a small update.

Was this page helpful?
0 / 5 - 0 ratings