-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
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 mock reassigning to previously unexisting method #8631
Fix mock reassigning to previously unexisting method #8631
Conversation
1a08a06
to
fab1852
Compare
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.
Awesome @lucasfcosta, what an awesome first contribution on a nicely isolated bug :)
fab1852
to
10f63b9
Compare
Codecov Report
@@ Coverage Diff @@
## master #8631 +/- ##
==========================================
- Coverage 63.45% 63.43% -0.03%
==========================================
Files 274 274
Lines 11382 11388 +6
Branches 2770 2772 +2
==========================================
+ Hits 7223 7224 +1
- Misses 3546 3547 +1
- Partials 613 617 +4
Continue to review full report at Codecov.
|
9d346c4
to
5c68cff
Compare
The Azure task in the pipeline seems to have failed due to network issues in their connection.
CC @jeysal are you able to rerun it? I couldn't rerun the build myself. (No need to rush btw, just pinging you because I've just noticed the build there has failed for no apparent reason) |
On Azure, I actually don't think I can - just do |
5c68cff
to
1ad9864
Compare
@jeysal ah that makes sense. It seems like |
Actually, |
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Summary
Previously, when restoring a stubbed method that didn't exist in the
object
passed tospyOn
we would always reassign toobject[methodName]
the previous result ofobject[methodName]
.Since
object[methodName]
causes the prototype chain to be traversed if we don't findmethodName
inobject
itself, this could cause us to assign the method that was previously in the prototype to a method inobject
which did not exist before.This PR fixes #8625.
Also, @fongandrew actually deserves a lot of credit for this. He's done most of the hard work by providing an accurate report detailing what the expected behaviour should be and by helping out to get to the optimal solution. Thanks @fongandrew 💖
The Problem in Detail
You can reproduce this bug with the following snippet.
The test above would previously fail because
restoreAllMocks
would cause us to reassign thefunc
we originally got from the parent to thechild
. Then, when callingfunc
in thechild
after restoring it, the result would come from thechild
itself instead of reaching up to thefunc
inparent
.As per the description, the problem happens due to these lines.
Implementation Choices
One could argue that the correct behaviour here would be to mock the property in the
prototype
itself since accessingmethodName
would end-up reaching theprototype
anyway. MakingrestoreAllMocks
delete the property on the children might seem like patching unexpected behaviour with unexpected behaviour. On the other hand, mocking the function straight on theprototype
may cause all sorts of weird problems since one might not expect that since the rest of theapi
does mocks in theobj
itself and may also cause strange side-effects. Mocking methods in the "child
" itself also complies with the Principle of Least Astonishment.For the sake of comparison with similar libraries,
sinonjs
's current version seems to currently do the same (stub the property in the "child
") for stubs.Test plan
I have tested this with the exact piece of code which I used to demonstrate the bug was present. I also added an extra assertion to that test to prove that the
func
property also does not exist in thechild
anymore after restoration.To ensure that we would have precise feedback in tests, I added a separate test-case to ensure that the method is mocked in the
child
itself and not in its parent.I think these two tests are sufficient.
PS: The previous CI failure is because I had removed what my Mac thought was an "unused directive" because
fsevents
was available, but that is in fact necessary in CI as per this comment since it does not use a Mac (which therefore causes the directive to be valid 😆).