Skip to content
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

The module factory of jest.mock() is not allowed to reference any out-of-scope variables #2567

Closed
simonsmith opened this issue Jan 11, 2017 · 37 comments

Comments

@simonsmith
Copy link

simonsmith commented Jan 11, 2017

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

@cpojer
Copy link
Member

cpojer commented Jan 11, 2017

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.

@cpojer cpojer closed this as completed Jan 11, 2017
@cpojer
Copy link
Member

cpojer commented Jan 11, 2017

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.

@simonsmith
Copy link
Author

Ah ha, that's the bit I couldn't suss. Thanks!

@cpojer
Copy link
Member

cpojer commented Jan 11, 2017

This one usage is ok and there is an escape hatch for it. Call your variable mockFoo.

@albertolive
Copy link

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?

@cpojer
Copy link
Member

cpojer commented Mar 9, 2017

yes.

vlad-bezden added a commit to vlad-bezden/React-Design-Patterns-and-Best-Practices that referenced this issue Mar 30, 2017
based on FB GitHub trace  jestjs/jest#2567 you can't reference external data inside of mock function. You will get following error

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

To fix this you need to declare local variable instead
@tutts
Copy link

tutts commented Apr 3, 2017

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

@qiu8310
Copy link

qiu8310 commented Jul 2, 2017

@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

@coolgod
Copy link

coolgod commented Nov 2, 2017

@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?

@cpojer
Copy link
Member

cpojer commented Nov 3, 2017

Only if you call jest.resetModules() between the two require calls.

@aaronbeall
Copy link

How do you make this work with ES6 modules, which cannot be put inside the function scope?

@SimenB
Copy link
Member

SimenB commented Nov 15, 2017

you can use the import function, along with e.g. https://github.com/airbnb/babel-plugin-dynamic-import-node

@aaronbeall
Copy link

aaronbeall commented Nov 16, 2017

@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?

@SimenB
Copy link
Member

SimenB commented Nov 16, 2017

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

@faucct
Copy link

faucct commented Dec 27, 2017

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.

@manuelbieh
Copy link

manuelbieh commented Jan 31, 2018

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

@faucct
Copy link

faucct commented Jan 31, 2018

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

@manuelbieh
Copy link

Not entirely sure yet since there are other things failing now ( 😄 ) but looks like it really helps, yes. Thanks! 🙂

@tomitrescak
Copy link

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".

@faucct
Copy link

faucct commented Feb 3, 2018

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.

@Soontao
Copy link

Soontao commented Apr 25, 2018

I meet this problem when I run jest with nodejs 10.0.0, just downgraded node version is work.

@SimenB
Copy link
Member

SimenB commented Apr 25, 2018

@Soontao I cannot reproduce that, are you able to set up a small reproduction?

@Soontao
Copy link

Soontao commented Apr 25, 2018

@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.

@douo
Copy link

douo commented Apr 26, 2018

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)

@SimenB
Copy link
Member

SimenB commented Apr 26, 2018

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...

@redbar0n
Copy link

Upgrading babel-jest with yarn add --dev babel-jest babel-core regenerator-runtime fixed this error for me.

@nckblu
Copy link

nckblu commented Feb 11, 2019

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

sqs added a commit to sourcegraph/sourcegraph-public-snapshot that referenced this issue May 12, 2019
This helps prepare for using Babel to transpile TypeScript but is good practice regardless.

- In `code_intelligence.test.tsx`, the `jest.mock('react-dom', ...)` was rejected with a fatal error by Babel (in babel-jest) because it referred to an out-of-scope variable (for why it rejects this, see jestjs/jest#2567). Also, it was undefined behavior that the `jest.mock` of `react-dom` applied to other modules (see "...mocked only for the file that calls `jest.mock`..." at https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options). So, this commit passes the `react-dom` `render` function as an argument to the functions under test to make mocking easier and fully explicit without the need for test harness magic.
- Removes unnecessary mocking of `react-dom` in `text_fields.test.tsx`. The mock was never checked.
sqs added a commit to sourcegraph/sourcegraph-public-snapshot that referenced this issue May 12, 2019
)

This helps prepare for using Babel to transpile TypeScript but is good practice regardless.

- In `code_intelligence.test.tsx`, the `jest.mock('react-dom', ...)` was rejected with a fatal error by Babel (in babel-jest) because it referred to an out-of-scope variable (for why it rejects this, see jestjs/jest#2567). Also, it was undefined behavior that the `jest.mock` of `react-dom` applied to other modules (see "...mocked only for the file that calls `jest.mock`..." at https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options). So, this commit passes the `react-dom` `render` function as an argument to the functions under test to make mocking easier and fully explicit without the need for test harness magic.
- Removes unnecessary mocking of `react-dom` in `text_fields.test.tsx`. The mock was never checked.
jasonmerino pushed a commit to jasonmerino/react-native-simple-store that referenced this issue May 22, 2019
…) (#55)

* Updated react native dependencies.

* Updated babel dependencies. Removed the caret from react and react-native beacause of incorrect peer dependencies.

* Changed from .babelrc to babel.config.js (based on facebook/react-native#21075).

* Add mock prefix to allow out of scope referencing (jestjs/jest#2567)

* Mock the async-storage module.

* Added explanation to README.

* Changed version to 2.0.0 as it is a breaking change.
@maksnester
Copy link

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 }]
  ]
}

@khryshyn
Copy link

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

`

@maxletourneur
Copy link

maxletourneur commented Jul 26, 2019

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

@ghost23
Copy link

ghost23 commented Dec 11, 2019

@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?

@philip-swrve
Copy link

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

@alisajadih
Copy link

hi, i have this problem too

@princ09
Copy link

princ09 commented Apr 24, 2021

@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.

@MaRuifeng
Copy link

MaRuifeng commented Apr 29, 2021

@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.

@github-actions
Copy link

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.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 30, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests