-
-
Notifications
You must be signed in to change notification settings - Fork 6.4k
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
Jest does not restore mocks on methods inherited from a prototype properly #8625
Comments
I have just confirmed this is easily reproducible in the current version of To confirm I've written the following test in it('supports mocking value in the prototype chain', () => {
const parent = {func: () => 'abcd'};
const child = Object.create(parent);
moduleMocker.spyOn(parent, 'func').mockReturnValue('jklm');
expect(child.func()).toEqual('jklm'); // works just fine
}); The test above passes ✅. When adding a it('supports mocking value in the prototype chain after restoration', () => {
const parent = {func: () => 'abcd'};
const child = Object.create(parent);
moduleMocker.spyOn(child, 'func').mockReturnValue('efgh');
moduleMocker.restoreAllMocks();
moduleMocker.spyOn(parent, 'func').mockReturnValue('jklm');
expect(child.func()).toEqual('jklm'); // is equal 'abcd'
}); (btw @fongandrew provided an amazingly good report 💖 ) As per the description, the problem happens due to these lines. Here we do an assigment to One could argue that the correct behaviour here would be to mock the property in the const sinon = require('sinon')
const a = { val: () => 1 }
const b = Object.create(a);
sinon.stub(b, 'val').returns(2);
b.val(); // 2
a.val(); // 1 ImplementationAs I went into investigating this anyway (and since it was a quick fix) I created a branch which contains the necessary changes. However, since @fongandrew has already brilliantly done the hardest part of this which is uncovering the bug, providing reproducibility steps and even a reproducible repo I don't want to open a PR if he wants to give it a try himself. In case we need to discuss possible implementations or desired behaviour, here's a link to the commit with this fix. If everyone's happy with it I can open a PR with the commit above. |
Thanks! I'm happy to defer to @lucasfcosta on the actual fix. I'm not super familiar with Jest's implementation. I'm curious why we need to crawl up the object's prototype chain in the proposed fix though (https://github.com/lucasfcosta/jest/blob/ba1885ac06a06f53ab18f13abefbd9157037cbc9/packages/jest-mock/src/index.ts#L1012-L1015). In my mind, I though it'd just be something like this: const isMethodOwner = object.hasOwnProperty(methodName); And the restore would just delete if it was untrue (the prototype still "owns" the original, so subsequent invocations would still go to the prototype in the absence of anything assigned to the child object). if (isMethodOwner) {
object[methodName] = original;
} else {
delete object[methodName];
} |
@fongandrew that's definitely true. I over-engineered this. Crawling up the I guess that's a relic from the previous attempt I've had at stubbing the method up in the You are absolutely right in this. I'm gonna update that branch accordingly when I get home today. Btw, by all means please feel free to open the PR before if you want to, you've done most of the hard work 💖 |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
🐛 Bug Report
When spying on an object or class, it is possible to mock a method that's defined higher up on the prototype chain. But when the original implementation is restored, the parent object's method is assigned to the child's.
To Reproduce
The second test case above fails with the first test case is present, but passes if the first test case does not exist. If you console log / debugger this, you'll see that the first test is adding a new method to B's prototype, and
restoreAllMocks
"restored" the mock from the first test by assigningA.test
toB.test
.Expected behavior
Mock restoration should work regardless of whether the method is defined higher up on the prototype chain or not.
If
B.hasOwnProperty('test')
is false, whenjest.spyOn(B, 'test')
is restored, Jest should just deleteB.test
rather than assign a value that might have existed only on B's prototype.Link to repl or repo
Example above ☝️but here's a minimum setup with the latest version of Jest if needed: https://github.com/fongandrew/jest-proto-repo. Also includes a repro with an ES6 class inheritance example.
npx envinfo --preset jest
The text was updated successfully, but these errors were encountered: