Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix onCall(#) with chained behaviors #1738

Merged
merged 2 commits into from
Mar 28, 2018
Merged

Conversation

nfriedly
Copy link
Contributor

@nfriedly nfriedly commented Mar 15, 2018

Purpose (TL;DR) - mandatory

Restores previous Sinon behavior and fixes issue #1736 by making all chained behaviors (yields, returns, throws, etc.) added after an .onCall(#) apply to that call rather than only the first behavior applying, and subsequent ones reverting to the default behavior.

Background (Problem in detail) - optional

As mentioned in the original issue, in previous versions of Sinon, the following code added a yeilds and a returns behavior to call 0:

stub.onCall(0).yields(x).returns(y);

However, in the current release of Sinon, that code would cause the first call to yield x and return undefined, and then all subsequent calls would return y.

The codebase I'm working on currently, uses the above pattern in numerous places for mocking http.request (which both returns and yields), and upgrading to the current version of Sinon caused a number of tests to start failing.

Solution - optional

behaviorMethods always return this (the behavior object) instead of the original stub. I also changed the behavior object to be a function that calls through to the original stub.

WARNING

It's possible that some users inadvertently depend on the buggy behavior with code such as this:

var stub = sinon.stub().onCall(0).returns('foo');
stub.returns('bar');

In the current release, the first call will return 'foo', and subsequent calls will return 'bar'. With the fix, it will return 'bar' and then undefined. However, I would argue that the above code is buggy and this fix merely exposes that bug, and therfore this fix shouldn't constitute a breaking change.

How to verify - mandatory

  1. Check out this branch
  2. npm install
  3. npm test

Checklist for author

  • npm run lint passes
  • References to standard library functions are cached.

Currently returns doesn't work with that combination
See issue sinonjs#1736
e.g. this code:

stub.onCall(0).yields(x).returns(y)

will once again both call the callback with x and return y.

This feature was present in v1.17.7, but lost somewhere between there and v4.4.5

Fixes sinonjs#1736
Copy link
Contributor

@fatso83 fatso83 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks fine. Good to have documenting tests.

I somewhat agree with you, but how would one make a stub that returns bar in most cases, but 'foo' in the 42nd call when using your fix?

@@ -101,7 +101,7 @@ function callCallback(behavior, args) {

var proto = {
create: function create(stub) {
var behavior = extend({}, proto);
var behavior = extend(stub.bind(), proto);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this do? I am guessing this is an easy way of wrapping the stub in a closure so you can change the copy instead of the original. am I right?

Copy link
Contributor Author

@nfriedly nfriedly Mar 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. We could replace it with

var behavior = extend(function(){
   var args = Array.prototype.slice.call(arguments);
   return stub.apply(null, args);
}, proto);

But the .bind version is more concise.

We don't want to extend the stub directly because that would overwrite other things.

@nfriedly
Copy link
Contributor Author

nfriedly commented Mar 16, 2018

how would one make a stub that returns bar in most cases, but 'foo' in the 42nd call when using your fix?

Like this:

var stub = sinon.stub();
stub.returns('foo');
stub.onCall(42).returns('bar');

(The second two lines could be swapped without changing the behavior.)

Or, a chained version:

var stub = sinon.stub().returns('foo').onCall(42).returns('bar');

(The behavior depends on the order here.)

We could perhaps add a method to get back to the default behavior, e.g.

var stub = sinon.stub().onCall(42).returns('bar').otherwise().returns('foo');

and otherwise() would just be a function that returned this.stub. Probably needs a better name, though.

And, like I said, this change restores the behavior that was present in Sinon 1.x. I'm not exactly sure when it changed, but I'm sure we could track it down if it matters.

@fatso83
Copy link
Contributor

fatso83 commented Mar 28, 2018

@mroderick This seemed fine and I finally understood the nuances of the implications, but I am not sure this should result in a new major or a patch! It is an API change, but as the previous change never was official/published I regard this as more of a bugfix that gives us the old behaviour back.

@fatso83
Copy link
Contributor

fatso83 commented Mar 28, 2018

I chose to release this as 4.4.10, given that I think this should be regarded as a bug fix.

@fatso83
Copy link
Contributor

fatso83 commented Mar 28, 2018

And thanks, @nfriedly, for the great work!

@mroderick
Copy link
Member

Good work team!

mroderick added a commit that referenced this pull request Mar 30, 2018
This reverts commit cee8156, reversing
changes made to f2696bd.
franck-romano pushed a commit to franck-romano/sinon that referenced this pull request Oct 1, 2019
This reverts commit cee8156, reversing
changes made to f2696bd.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants