Fix mock reassigning to previously unexisting method#8631
Conversation
1a08a06 to
fab1852
Compare
jeysal
left a comment
There was a problem hiding this comment.
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
objectpassed tospyOnwe would always reassign toobject[methodName]the previous result ofobject[methodName].Since
object[methodName]causes the prototype chain to be traversed if we don't findmethodNameinobjectitself, this could cause us to assign the method that was previously in the prototype to a method inobjectwhich 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
restoreAllMockswould cause us to reassign thefuncwe originally got from the parent to thechild. Then, when callingfuncin thechildafter restoring it, the result would come from thechilditself instead of reaching up to thefuncinparent.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
prototypeitself since accessingmethodNamewould end-up reaching theprototypeanyway. MakingrestoreAllMocksdelete the property on the children might seem like patching unexpected behaviour with unexpected behaviour. On the other hand, mocking the function straight on theprototypemay cause all sorts of weird problems since one might not expect that since the rest of theapidoes mocks in theobjitself 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
funcproperty also does not exist in thechildanymore 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
childitself 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
fseventswas 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 😆).