What did you expect to happen?
When I create a mock on a class and try to control the behavior of one of its functions using withArgs
, an exception is thrown. Happens only when using multiple calls of withArgs
.
What actually happens
How to reproduce
Reproducible example
Assume I declared a class C
:
class C {
static foo(arg1, arg2) {return 'foo';}
}
I try to control the behavior of foo
using a mock on C
:
it('test withArgs mock', () => {
let CMock = sinon.mock(C);
let fooStub = CMock.expects('foo');
fooStub.withArgs('a', 'b').returns(1);
fooStub.withArgs('c', 'd').returns(2);
expect(fooStub('a', 'b')).toEqual(1);
expect(fooStub('c', 'd')).toEqual(2);
CMock.restore();
});
Which throws the following exception:
ExpectationError: foo received wrong arguments ["a", "b"], expected ["c", "d"]
at Object.fail (node_modules/sinon/lib/sinon/mock-expectation.js:281:25)
at Function.(node_modules/sinon/lib/sinon/mock-expectation.js:182:33)
at Array.forEach (native)
at Function.verifyCallAllowed (node_modules/sinon/lib/sinon/mock-expectation.js:175:27)
at Function.invoke (node_modules/sinon/lib/sinon/mock-expectation.js:78:14)
at proxy (node_modules/sinon/lib/sinon/spy.js:89:22)
While this code works:
it('test withArgs stub', () => {
let fooStub = sinon.stub(C, 'foo');
fooStub.withArgs('a', 'b').returns(1);
fooStub.withArgs('c', 'd').returns(2);
expect(fooStub('a', 'b')).toEqual(1);
expect(fooStub('c', 'd')).toEqual(2);
fooStub.restore();
});
It is worth mentioning that the following scenarios work as expected:
it('test one withArgs mock', () => {
let CMock = sinon.mock(C);
let fooStub = CMock.expects('foo');
fooStub.withArgs('a', 'b').returns(1);
expect(fooStub('a', 'b')).toEqual(1); // ok
CMock.restore();
});
it('test one withArgs mock', () => {
let CMock = sinon.mock(C);
let fooStub = CMock.expects('foo');
fooStub.withArgs('a', 'b').returns(1);
expect(fooStub('c', 'd')).toEqual(1); // error: foo received wrong arguments ["c", "d"], expected ["a", "b"]
CMock.restore();
});
Meaning, the problem occurs only when using withArgs
multiple times.
It might be related to #1381, but as you can see in the example, the solution suggested there does not work.
Thanks for a examplary issue report. I don't use the mocks functionality, as it makes my brain hurt, so someone else will need to have a look at this, but your examples should make that a lot easier.
Feel free to have a look at the code yourself - often the quickest way of getting this fixed :-)
I got a look at the code.
First of all, this test works:
class C {
static foo(arg1, arg2) {return 'foo';}
}
it('test withArgs mock', () => {
let CMock = sinon.mock(C);
CMock.expects('foo').withArgs('a', 'b').returns(1);
CMock.expects('foo').withArgs('c', 'd').returns(2);
expect(C.foo('a', 'b')).toEqual(1);
expect(C.foo('c', 'd')).toEqual(2);
CMock.restore();
});
There are two reasons why this works and the test in the original issue doesn't:
expects
.expects
.Meaning, this doesn't work either:
it('test withArgs mock', () => {
let CMock = sinon.mock(C);
CMock.expects('foo').withArgs('a', 'b').returns(1);
let fooStub = CMock.expects('foo').withArgs('c', 'd').returns(2);
expect(fooStub('a', 'b')).toEqual(1); // Error: foo received wrong arguments ["a", "b"], expected ["c", "d"]
expect(fooStub('c', 'd')).toEqual(2);
CMock.restore();
});
Why does it happen?
Every time expects
is called, the following happens:
minCalls/maxCalls
- which are the variables that are changed when you call once()
for example) is created, only that it is a "scoped stub", it doesn't have connection to other expects
. It is the object returned from expects
.Which results in different behaviors for these scenarios:
expects
, it will be limited only to that expects
.expects
and find one that fits. If there is more than one, it will call the first.Is this the correct behavior? It seems to me more intuitive that once someone calls expects
on a method, it will behave exactly like a stub
(with the additional minCalls/maxCalls
). This will result in:
stub
and mock-expectation
.Now, what would happen to minCalls/maxCalls
?
Since there is only one expectation
now, I propose three alternative ways to do call count verification with mocks:
once()
, twice()
etc. let fooStub = CMock.expects('foo').withArgs('a', 'b').returns(1).twice();
fooStub.withArgs('c', 'd').returns(2).once(); // Does not affect the previous expectation.
Might be a bit confusing though.
once()
, twice()
etc. CMock.expects('foo').withArgs('a', 'b').returns(1).twice(); // `twice()` returns `null`, so that one has to call `expects()` again to set expectations for other arguments.
CMock.expects('foo').withArgs('c', 'd').returns(2).once();
More clear, but you cannot stub foo
more than once so we will need a way to bypass that. Also, this is still possible:
CMock.expects('foo').withArgs('a', 'b').returns(1).withArgs('c', 'd').returns(2).twice(); // I guess that this should only affect the last `withArgs`?
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Most helpful comment
I got a look at the code.
First of all, this test works:
There are two reasons why this works and the test in the original issue doesn't:
expects
.expects
.Meaning, this doesn't work either:
Why does it happen?
Every time
expects
is called, the following happens:minCalls/maxCalls
- which are the variables that are changed when you callonce()
for example) is created, only that it is a "scoped stub", it doesn't have connection to otherexpects
. It is the object returned fromexpects
.Which results in different behaviors for these scenarios:
expects
, it will be limited only to thatexpects
.expects
and find one that fits. If there is more than one, it will call the first.Is this the correct behavior? It seems to me more intuitive that once someone calls
expects
on a method, it will behave exactly like astub
(with the additionalminCalls/maxCalls
). This will result in:stub
andmock-expectation
.Now, what would happen to
minCalls/maxCalls
?Since there is only one
expectation
now, I propose three alternative ways to do call count verification with mocks:once()
,twice()
etc.Might be a bit confusing though.
once()
,twice()
etc.More clear, but you cannot stub
foo
more than once so we will need a way to bypass that. Also, this is still possible: