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 #1372: make sandbox.resetHistory also reset spies #1424
Fix #1372: make sandbox.resetHistory also reset spies #1424
Conversation
lib/sinon/collection.js
Outdated
return; | ||
} | ||
|
||
if (typeof fake.reset === "function" && /^spy\#/.test(fake.id)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What else has no resetHistory
but does have a reset
method besides a spy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None that I know of. Do you think I should just test for a reset
method, and call it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe so. I believe I've already seen code like that in another pr (if resetHistory else-if reset).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. I've pushed a new commit that just tests for the presence of reset
before calling it
lib/sinon/collection.js
Outdated
getFakes(this).forEach(function (fake) { | ||
if (typeof fake.resetHistory === "function") { | ||
fake.resetHistory(); | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use else-if
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No thanks, there is no else if
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I definitely agree with point number 2. I'm even working on a PR related to that. But point number one is a little misguided.
- Nothing is special about
else-if
in javascript. The logic is the same in every language that I can think of. You can always rewrite an else-if as a 3 statements instead of 2. It's a core tenant of Logic. - Splashing random returns around seems less explicit and it's obscuring the intent. Is the goal of this code to
return undefined
? - The most important difference to me is consistency. We use else-if throughout the code. In my opinion, it reads better and is easier to reason about.
Another problem I foresee is future work. So let's say there was something I wanted to do no matter which fork of the if
branch the code went down.. I'd have to "fix" your code first before I could accomplish that. In an if-else
case, the change would be purely additive.
How would you feel adding a (pointless) return
statement to the other if for consistency's sake?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'll take the bait.
If follow your original suggestion, while writing JavaScript without omitting syntax, we'll end up with this:
getFakes(this).forEach(function (fake) {
if (typeof fake.resetHistory === "function") {
fake.resetHistory();
} else {
if (typeof fake.reset === "function") {
fake.reset();
}
}
});
In my opinion, that requires a lot more effort to understand.
Using return
as a control flow statement helps me keep indentation low, which I think we all agree makes code easier to read.
Regarding consistency: I don't think we're that consistent in this codebase. We've been using return
as a control flow statement across this codebase for awhile, try this search:
$ ag --ignore=docs/ -Q "return;"
I am all for consistency across a codebase ... until we find a better way of doing things, then we should slowly refactor to adopt the better practice. I believe that using return
to reduce indentation is a good practice.
How would you feel adding a (pointless) return statement to the other if for consistency's sake?
ESLint understands how to keep consistent return, which is configured in eslint-config-sinon
.
I think it is known to any (non-novice) reader, that the default return value of a function undefined
.
</bike-shed>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion, that requires a lot more effort to understand.
I would get the above code fixed in code review. It should be an else-if
. I agree that the way it is written is more complex than an else-if
although logically congruent.
Using return as a control flow statement helps me keep indentation low, which I think we all agree makes code easier to read.
I believe that using return to reduce indentation is a good practice.
We agree there, but an else-if
is the same amount of indentation as a plain if
:
if (foo) {
// one indentation
return;
}
if (bar) {
// one indentation
}
// VS
if (foo) {
// one indentation
} else if {
// one indentation
}
You're not saving anything whatsoever in this case. You're actually typing a few extra characters if you're concerned about keystroke counts at all.
try this search
We use empty returns all the time. That is not the/a problem. You're not getting the full story though.
ag --ignore=docs/ -Q "return;" -A 5 -B 3
I only found one instance of if-return-if
usage. PS. I had been putting off installing/using ag
until just now.
RE: consistency
Apparently every fake has resetHistory
or reset
so we should remove the second if and make it an if-else
like in spy:
if (this.fakes) {
this.fakes.forEach(function (fake) {
if (fake.resetHistory) {
fake.resetHistory();
} else {
fake.reset();
}
});
}
Then this whole conversation goes away. 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or just fake fake.reset()
directly
spy(); | ||
assert.equals(spy.callCount, 2); | ||
|
||
this.sandbox.resetHistory(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming there are already related tests for stubs. It wouldn't hurt to have a test with both since you're potentially affecting both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have expanded the test for collection.resetHistory
to have a fake that looks like a spy in it
e55de8d
to
0500231
Compare
3c860e9
to
d52c92d
Compare
getFakes(this).forEach(function (fake) { | ||
var method = fake.resetHistory || fake.reset; | ||
|
||
if (method) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this check necessary? What is it catching?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Situations where neither method exists
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which is a bit synthetic right now .... I could also just call fake.reset
, since that is what stub.resetHistory does ... but that just seems wrong
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're going for the check, shouldn't it throw if there is no way to reset the thing this is called on?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're going for the check, shouldn't it throw if there is no way to reset the thing this is called on?
I didn't want to create something that was vastly different from what each
does in the same file.
https://github.com/sinonjs/sinon/blob/master/lib/sinon/collection.js#L19-L28
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, it filtered non-function values before iterating. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are they fakes that have neither resetHistory
nor reset
? It seems like we could simplify this a bit and be in the same place:
(fake.resetHistory || fake.reset).call(fake);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you stub a complete object the fake has no resetHistory()
nor reset()
:
function Test() {}
Test.prototype.foo = function () {}
Test.prototype.bar = function () {}
let sandbox = Sinon.sandbox.create();
sandbox.stub(Test.prototype);
sandbox.resetHistory();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be useful to add a note about why resetHistory
is calling reset
for future contributors.
What do you think about renaming/aliasing spy.reset
to spy.resetHistory
and deprecating reset
? Not for this ticket, but just putting the idea out there.
I tested the change in my local environment and it solves the issue #1372 reported by me 👍 |
I think that would be a good improvement. Will you add a ticket for it and describe how it would work? Perhaps we can even attract a new contributor with it ;) |
I don't think there is anything else to add here. Like @mroderick suggested, we could think about refactoring the whole thing. |
I have published this change to npm as |
This PR fixes #1372, by improving the implementation of
sandbox.resetHistory()
methodTo verify
npm install
npm test