Jest: The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables

Created on 11 Jan 2017  ·  33Comments  ·  Source: facebook/jest

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 :)

Most helpful comment

Using jest.doMock instead of jest.mock has helped me.

All 33 comments

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 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>);

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')

Was this page helpful?
0 / 5 - 0 ratings