The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables
simonsmith opened this issue · comments
I'm using the snippet from #1960 to mock Picker
in RN
import React, {Component} from 'react';
jest.mock(`Picker`, () => {
// ...etc
});
Works fine in Jest 17, throws following error in Jest 18:
/Users/simonsmith/Sites/new-look/newlookapp/test/unit/setup.js: babel-plugin-jest-hoist: The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables.
Invalid variable access: React
Whitelisted objects: Array, ArrayBuffer, ..... etc
I'm using React 15.4.2 and RN 0.40
I tried babel-jest@test
and they run as expected but all my snapshots fail, looks like more props are coming through which is probably unrelated to this.
Anything I can do to fix this now or should I wait for the next release for babel-jest
?
Thanks :)
you need to do this:
jest.mock(`Picker`, () => {
const React = require('react');
});
This used to be a bug that we fixed. In a mock you can only require things locally and you aren't allowed to access external variables.
To explain why: With jest.resetModules()
you may reset all currently available modules, so when you call require, you'll get a new version of each module. If you use React from the top level, you'll end up having potentially two copies of React.
Ah ha, that's the bit I couldn't suss. Thanks!
This one usage is ok and there is an escape hatch for it. Call your variable mockFoo
.
But, If I have multiple mocks:
jest.mock('./OfferList', () => 'OfferList');
jest.mock('./OfferHeader', () => 'OfferHeader');
jest.mock('./OfferHiredModal', () => 'OfferHiredModal');
Do I have to putconst React = require('React');
in every single mock?
yes.
jest.mock(`Picker`, () => {
const React = require('React');
});
in case anyone copy pastes this and sees it failing in CI (circle/gitlab) and not their local, make sure React
is a lowercase react
jest.mock(`Picker`, () => {
const React = require('react');
});
@cpojer I want to use __dirname
variable, it is also not allowed, how can I get it?
I don't want to use a environment involved path, like /Users/xx/project
@cpojer I don't really understand your explanation:
If you use React from the top level, you'll end up having potentially two copies of React.
If I require React locally, I will also have two copies of local React, right?
Only if you call jest.resetModules()
between the two require calls.
How do you make this work with ES6 modules, which cannot be put inside the function scope?
you can use the import
function, along with e.g. https://github.com/airbnb/babel-plugin-dynamic-import-node
@SimenB Thanks... can you give an example? I'm using TypeScript which supports dynamic imports but I'm not clear how this would work because then the mock implementation becomes async, does Jest know how to wait for the mock to resolve before continuing with test cases?
Just await the promise.
let myDep;
beforeEach(async () => {
jest.resetModules();
myDep = await import('./some-modules.js');
})
No idea how that looks with typescript, but shouldn't be too different
I am having trouble mocking with a function using ES6 syntax inside an example:
/**
* @jest-environment jsdom
*/
import SagaTester from "redux-saga-tester";
import supportRequests from "sagas/support_requests";
import reducers from "reducers";
import { fetched } from "actions/support_requests";
describe("success", () => {
it("sends the message via connection", async () => {
const sagaTester = new SagaTester({
reducers: reducers,
initialState: {
router: { location: { pathname: "/support_requests" } },
supportRequests: null,
authenticated: true,
currentUser: { isSupportAgent: true },
},
});
jest.mock("sagas/oauth", () => {
return {
...require.requireActual("sagas/oauth"),
callFetch: function* () {
return { ok: true, json: () => Promise.resolve({ support_requests: [], meta: { pagination: {} } }) };
},
};
});
sagaTester.start(supportRequests);
await sagaTester.waitFor(fetched);
});
});
The spread operator (...) and generator function get transformed by a babel into something using _extends
and regeneratorRuntime
accordingly which cannot be accessed:
babel-plugin-jest-hoist: The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables.
Invalid variable access: _extends
Whitelisted objects: Array, ArrayBuffer, Boolean, DataView, Date, Error, EvalError, Float32Array, Float64Array, Function, Generator, GeneratorFunction, Infinity, Int16Array, Int32Array, Int8Array, InternalError, Intl, JSON, Map, Math, NaN, Number, Object, Promise, Proxy, RangeError, ReferenceError, Reflect, RegExp, Set, String, Symbol, SyntaxError, TypeError, URIError, Uint16Array, Uint32Array, Uint8Array, Uint8ClampedArray, WeakMap, WeakSet, arguments, expect, jest, require, undefined, console, DTRACE_NET_SERVER_CONNECTION, DTRACE_NET_STREAM_END, DTRACE_HTTP_SERVER_REQUEST, DTRACE_HTTP_SERVER_RESPONSE, DTRACE_HTTP_CLIENT_REQUEST, DTRACE_HTTP_CLIENT_RESPONSE, global, process, Buffer, clearImmediate, clearInterval, clearTimeout, setImmediate, setInterval, setTimeout.
Note: This is a precaution to guard against uninitialized mock variables. If it is ensured that the mock is required lazily, variable names prefixed with `mock` are permitted.
Has anyone experienced the issue before? I use latest jest.
Getting the same error but with:
Invalid variable access: _asyncToGenerator
I'm using the babel-plugin-transform-regenerator. How can I get jest to not complain about "The module factory of jest.mock()
" not being "allowed to reference any out-of-scope variables" in this case?!
// edit:
Full test is
it('should request data via API', async () => {
const store = mockStore({ fields: initialState });
jest.resetModules();
jest.mock('services/api-client', () => ({
getFieldsByFarm: async () => {
return [{ field: {} }];
},
}));
const Actions = require('./actions');
const expected = [
{ type: 'FIELDS/REQUEST' },
{ type: 'FIELDS/RECEIVE', payload: { items: [{ field: {} }], didInvalidate: false },},
];
await store.dispatch(Actions.getFieldsByFarm());
const dispatchedActions = store.getActions();
expect(dispatchedActions[0]).toEqual(expected[0]);
expect(dispatchedActions[1].payload).toEqual(expect.objectContaining(expected[1].payload));
});
Wrapping some parts of the test in an async IIFE and removing the async
in front of the test function makes jest not throw the error:
it('should request data via API', (done) => {
const store = mockStore({ fields: initialState });
jest.resetModules();
jest.mock('services/api-clients/nana', () => ({
getFieldsByFarm: async () => {
return [{ field: {} }];
},
}));
const Actions = require('./actions');
(async () => {
const expected = [
{ type: 'FIELDS/REQUEST' },
{
type: 'FIELDS/RECEIVE',
payload: { items: [{ field: {} }], didInvalidate: false },
},
];
await store.dispatch(Actions.getFieldsByFarm());
const dispatchedActions = store.getActions();
expect(dispatchedActions[0]).toEqual(expected[0]);
expect(dispatchedActions[1].payload).toEqual(expect.objectContaining(expected[1].payload));
done();
})();
});
Using jest.doMock
instead of jest.mock
has helped me.
Not entirely sure yet since there are other things failing now ( 😄 ) but looks like it really helps, yes. Thanks! 🙂
Any idea why doMock
works and mock
does not? Weird bit for me was also that if I put the variable with name "MockedComponent" I received an error, but when I put "mockedComponent" there was no error, but the reference was "undefined".
The ‘jest.mock’ calls get moved from ‘it’ calls to the outer closure by a preprocessor and it does not work very well. ‘jest.doMock’ calls aren’t affected by a preprocessor.
I meet this problem when I run jest with nodejs 10.0.0
, just downgraded node version is work.
@Soontao I cannot reproduce that, are you able to set up a small reproduction?
@SimenB
Thanks for your quickly reply, but when I try to reproduce that with node v10
, I found that all tests work fine, I think the problem maybe caused by other reasons, and I lost them when I reinstall nodejs.
Same issue when run with nodejs 10.0.0
/xxx/node_modules/react-native/jest/setup.js: babel-plugin-jest-hoist: The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables.
Invalid variable access: console
Whitelisted objects: Array, ArrayBuffer, Boolean, DataView, Date, Error, EvalError, Float32Array, Float64Array, Function, Generator, GeneratorFunction, Infinity, Int16Array, Int32Array, Int8Array, InternalError, Intl, JSON, Map, Math, NaN, Number, Object, Promise, Proxy, RangeError, ReferenceError, Reflect, RegExp, Set, String, Symbol, SyntaxError, TypeError, URIError, Uint16Array, Uint32Array, Uint8Array, Uint8ClampedArray, WeakMap, WeakSet, arguments, expect, jest, require, undefined, DTRACE_NET_SERVER_CONNECTION, DTRACE_NET_STREAM_END, DTRACE_HTTP_SERVER_REQUEST, DTRACE_HTTP_SERVER_RESPONSE, DTRACE_HTTP_CLIENT_REQUEST, DTRACE_HTTP_CLIENT_RESPONSE, global, process, Buffer, clearImmediate, clearInterval, clearTimeout, setImmediate, setInterval, setTimeout.
Note: This is a precaution to guard against uninitialized mock variables. If it is ensured that the mock is required lazily, variable names prefixed with `mock` are permitted.
at invariant (node_modules/babel-plugin-jest-hoist/build/index.js:14:11)
at newFn (node_modules/babel-traverse/lib/visitors.js:276:21)
at NodePath._call (node_modules/babel-traverse/lib/path/context.js:76:18)
at NodePath.call (node_modules/babel-traverse/lib/path/context.js:48:17)
at NodePath.visit (node_modules/babel-traverse/lib/path/context.js:105:12)
at TraversalContext.visitQueue (node_modules/babel-traverse/lib/context.js:150:16)
That doesn't have anything to do with node 10, it's just that we don't have console
in the whitelist. PR welcome! We really should just use some globals
module instead of a manual whitelist...
Last one fixed here: #6075
Upgrading babel-jest with yarn add --dev babel-jest babel-core regenerator-runtime
fixed this error for me.
I just stumbled upon this while googling and it seems like I've missed this crucial line in the error message along with everyone else:
If it is ensured that the mock is required lazily, variable names prefixed with mock
are permitted.
Just change the name of what you're mocking to mockYourComponentName
I run into this issue after I add that code in my jest.conf, to add tsx support in tests (without that code, I can't write tsx in my spec.tsx files:
globals: {
jasmine: true,
+ 'ts-jest': {
+ babelConfig: true
+ }
}
module.exports = {
// eslint-disable-next-line no-undef
rootDir: path.resolve(__dirname, '../'),
roots: ['<rootDir>/src'],
verbose: false,
moduleFileExtensions: ['ts', 'tsx', 'vue', 'js', 'jsx', 'json'],
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(ts|js)x?$',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
transform: {
'^.+\\.vue$': 'vue-jest',
'^.+\\.(js|jsx)?$': 'babel-jest',
'^.+\\.tsx?$': 'ts-jest'
},
transformIgnorePatterns: ['<rootDir>/node_modules/(?!lodash-es)'],
snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
setupFilesAfterEnv: ['<rootDir>/test/jest.init.ts'],
// run tests with --coverage to see coverage
coverageDirectory: '<rootDir>/test/coverage',
coverageReporters: ['html', 'text-summary'],
collectCoverageFrom: ['src/**/*.{ts,tsx,js,jsx,vue}', '!**/node_modules/**'],
globals: {
jasmine: true,
'ts-jest': {
babelConfig: true
}
}
}
Error I got:
babel-plugin-jest-hoist: The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables.
Invalid variable access: _debounce
Whitelisted objects: Array, ArrayBuffer, Boolean, DataView, Date, Error, EvalError, Float32Array, Float64Array, Function, Generator, GeneratorFunction, Infinity, Int16Arra
y, Int32Array, Int8Array, InternalError, Intl, JSON, Map, Math, NaN, Number, Object, Promise, Proxy, RangeError, ReferenceError, Reflect, RegExp, Set, String, Symbol, SyntaxEr
ror, TypeError, URIError, Uint16Array, Uint32Array, Uint8Array, Uint8ClampedArray, WeakMap, WeakSet, arguments, console, expect, isNaN, jest, parseFloat, parseInt, require, un
defined, DTRACE_NET_SERVER_CONNECTION, DTRACE_NET_STREAM_END, DTRACE_HTTP_SERVER_REQUEST, DTRACE_HTTP_SERVER_RESPONSE, DTRACE_HTTP_CLIENT_REQUEST, DTRACE_HTTP_CLIENT_RESPONSE,
COUNTER_NET_SERVER_CONNECTION, COUNTER_NET_SERVER_CONNECTION_CLOSE, COUNTER_HTTP_SERVER_REQUEST, COUNTER_HTTP_SERVER_RESPONSE, COUNTER_HTTP_CLIENT_REQUEST, COUNTER_HTTP_CLIEN
T_RESPONSE, global, process, Buffer, clearImmediate, clearInterval, clearTimeout, setImmediate, setInterval, setTimeout.
Note: This is a precaution to guard against uninitialized mock variables. If it is ensured that the mock is required lazily, variable names prefixed with `mock` (case inse
nsitive) are permitted.
Code itself:
const DEBOUNCE_DELAY = 10
const _debounce = jest.requireActual('lodash-es/debounce').default
jest.mock('lodash-es/debounce', () =>
jest.fn((fn) => _debounce(fn, DEBOUNCE_DELAY))
)
I had to rewrite it with magic number and inline import:
jest.mock('lodash-es/debounce', () =>
jest.fn((fn) => jest.requireActual('lodash-es/debounce').default(fn, 10))
)
Notice, that without that config in globals ('ts-jest': { babelConfig: true }
) code worked fine. However without that line in config I was not able to run tests with tsx, I faced with that error:
Jest encountered an unexpected token
This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.
By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".
Here's what you can do:
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/en/configuration.html
Details:
C:\Users\Alend\vue-project\src\my-component\MyComponent.spec.tsx:20
render: function () { return <div id="foo">Foo</div>; }
^
Some versions from package.json:
"@babel/core": "^7.4.5",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "24.8.0",
"jest": "24.8.0",
"ts-jest": "24.0.2",
and the babel config itself:
module.exports = {
presets: [
[
'@babel/preset-env',
{
modules: 'commonjs',
targets: {
browsers: ['> 1%', 'last 2 versions', 'not ie <= 11']
}
}
],
'@vue/babel-preset-jsx'
],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-export-namespace-from',
'@babel/plugin-proposal-function-sent',
'@babel/plugin-proposal-json-strings',
'@babel/plugin-proposal-numeric-separator',
'@babel/plugin-proposal-throw-expressions',
'@babel/plugin-syntax-dynamic-import',
['@babel/plugin-transform-runtime', { corejs: 2 }],
['@babel/plugin-proposal-decorators', { legacy: true }]
]
}
Seems like such issue still exist and now even workarounds don't help in create react app application
`
ReferenceError: mockComponent is not defined
17 | const mockComponent = () => <div>Mock</div>;
18 |
> 19 | jest.mock('./components/Component', () => ({ Component: mockComponent }));
`
@khryshyn
Jest will automatically hoist jest.mock calls to the top of the module.
That's why your mockComponent
const is not defined yet when jest.mock runs.
To go around this "issue/feature", I do it in 2 steps as such:
jest.mock('./components/Component', () => ({ Component: jest.fn() }));
import { Component } from "./components/Component";
Component.mockImplementation(() => <div>Mock</div>);
@khryshyn
Jest will automatically hoist jest.mock calls to the top of the module.
That's why yourmockComponent
const is not defined yet when jest.mock runs.To go around this "issue/feature", I do it in 2 steps as such:
jest.mock('./components/Component', () => ({ Component: jest.fn() })); import { Component } from "./components/Component"; Component.mockImplementation(() => <div>Mock</div>);
Is this really correct? As @nckblu already mentioned above, variables that start with 'mock' should be available as an exception. And 'mockComponent' should fall into that exception, right?
In the meantime, if you want a workaround to add a debug statement e.g. console.log('Checking...'), prefix console.log with global to make it work.
global.console.log('global console working')
hi, i have this problem too
@alisajadih I am facing same , have you found solution for it?
import { log, loggers, logPageRender, LogLevel, setMetaDefaults } from '../../helpers/loggerWrapper';
i want to mock this.
@ghost23 I think the mock
prefix exception is only valid when the module factory that returns the mock is a higher-order-function (HOF), and the mock-prefixed variable is referenced at the inner layer. That's why this technique is generally used to mock classes because classes have constructors which are HOFs naturally.
This is mostly covered in calling-jestmock-with-the-module-factory-parameter. Quoted as below for your quick reference.
In order to mock a constructor function, the module factory must return a constructor function. In other words, the module factory must be a function that returns a function - a higher-order function (HOF).
With newer versions of node (14+), you could hit a different error message as shown in #10996.
The mockimplementation approach mentioned by @maxletourneur is a nice solution.
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.