instanceof not working on object returned by Rewire
Closed this issue ยท 9 comments
๐ Bug Report
instanceof
doesn't work as expected in Jest when being used on an object returned from Rewire. Please check out codes below.
I thought it was the problem of Rewire. However this issue can't be reproduced under pure node.js with Rewire.
I checked the prototype
of the remote object. It just looks like a normal object. Don't understand what made it unrecognizable by instanceof
.
To Reproduce
file rewired.test.js
const rewire = require('rewire')
const code = rewire('./rewired')
const here = {}
const there = code.__get__('another')
test('test', () => {
expect(here instanceof Object).toBeTruthy()
expect(there instanceof Object).toBeTruthy()
})
file rewired.js
const another = {}
Run npx envinfo --preset jest
Paste the results here:
System:
OS: macOS 10.14.4
CPU: (8) x64 Intel(R) Core(TM) i7-4960HQ CPU @ 2.60GHz
Binaries:
Node: 12.1.0 - /usr/local/bin/node
Yarn: 1.7.0 - /usr/local/bin/yarn
npm: 6.9.0 - /usr/local/bin/npm
npmPackages:
jest: ^24.7.1 => 24.7.1
Confirmed. Probably vm
related, following prints false
:
const vm =require('vm')
vm.runInNewContext(`
const rewire = require("rewire");
const code = rewire("./rewired");
const here = {};
const there = code.__get__("another");
console.log(there.__proto__ === here.__proto__);
`, {require,console})
Thank you for the analysis. An issue (#27632) has been submitted to node.js.
Engineers from node.js think it is a feature that all built-in objects such as Object
are newly created in each context. So it is normal that the references to it are different.
Using
vm.runInNewContext
is like creating a new browser window. All JavaScript has builtins like Object and Array but they are created for their individual context. That means that each browser window has it's ownObject
instance. These contexts are also calledrealm
. It is possible to cross the boundary of such context so one context may execute JavaScript code created in the other context. But the objects created in one now have a different baseObject
builtin instance than the objects created in the other context.instanceof
checks can therefore not work over the boundary.
That is exactly what happens here: here is created in the context created by
vm
while there is a reference to an object created in a different context. Theinstanceof
check tests if those objects are of the same instance of the current context and that's thevm
one. Thus the secondinstanceof
check fails.
Here are some test codes to simulate this issue.
const vm = require('vm')
const code = `
const rewire = require("rewire");
const code = rewire("./rewired")
const there = code.__get__("there");
const obj = code.__get__("Object");
console.log(there instanceof obj);
console.log(there instanceof Object);
`;
eval(code)
vm.runInNewContext(code, {require, console})
vm.runInNewContext(code, {require, console, Object})
Result is
true
true
true
false
true
true
It seems we can have two workarounds for this issue before it can be fixed. One is to use the base classes from rewire
instead of the builtin base classes. The other one is Jest to create an option is pass some object to new vm context.
In that case, this is actually the same issue as #2549
However, even though I knew about the realms problem, this surprises me, because I would have thought that rewire does not use require
but instead reads the modules using fs
or something, in which case I believe there would be no problem because both Object
s would be from inside the VM
Yes #2549 looks like the same issue.
I changed the code again and removed the rewire
dependency. Now it looks like below
test.js
const vm = require('vm')
const code = `console.log(require("./rewired").there instanceof Object);`;
eval(code)
vm.runInNewContext(code, {require, console})
vm.runInNewContext(code, {require, console, Object})
rewired.js
exports.there = {}
The result is
true
false
true
Obviously this issue has spreaded to the any code reference to the tested source file. This is inevitable in any kind of test. Issue will always happen when instanceof
operation is applied to the arguments of the function tested.
If Node is not willing to change this behaviour, I believe Jest need to do something here. Either discard the way of starting new vm context, or allow the user to pass in the base objects when needed.
Got confirmation from Node developer that objects should use the context of their definition, not execution. So they don't think this is a problem. But for Jest this is a problem.
Let's close this to keep any discussion centralized in #2549. It's a long standing issue and there's a lot of info and a bounty, but it's a complex topic that might not be properly fixed in the near future :|
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.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.