Jest: `jest.mock()` 的模块工厂不允许引用任何超出范围的变量

创建于 2017-01-11  ·  33评论  ·  资料来源: facebook/jest

我正在使用#1960 中的代码片段在 RN 中模拟Picker

import React, {Component} from 'react';

jest.mock(`Picker`, () => {
 // ...etc
});

在 Jest 17 中工作正常,在 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

我正在使用 React 15.4.2 和 RN 0.40

我尝试babel-jest@test并且它们按预期运行,但我所有的快照都失败了,看起来更多的道具正在通过,这可能与此无关。

我现在能做些什么来解决这个问题,还是应该等待下一个版本babel-jest

谢谢 :)

最有用的评论

使用jest.doMock而不是jest.mock对我有帮助。

所有33条评论

你需要这样做:

jest.mock(`Picker`, () => {
  const React = require('react');
});

这曾经是我们修复的错误。 在模拟中,您只能在本地要求内容,并且不允许访问外部变量。

解释原因:使用jest.resetModules()你可以重置所有当前可用的模块,所以当你调用 require 时,你会得到每个模块的新版本。 如果你从顶层使用 React,你最终可能会拥有两个 React 副本。

啊哈,这就是我无法忍受的一点。 谢谢!

这种用法是可以的,并且有一个逃生舱口。 调用你的变量mockFoo

但是,如果我有多个模拟:

jest.mock('./OfferList', () => 'OfferList');
jest.mock('./OfferHeader', () => 'OfferHeader');
jest.mock('./OfferHiredModal', () => 'OfferHiredModal');

我必须在每个模拟中都放const React = require('React');吗?

是的。

jest.mock(`Picker`, () => {
  const React = require('React');
});

万一有人复制粘贴并看到它在 CI (circle/gitlab) 而不是他们的本地失败,请确保React是小写react

jest.mock(`Picker`, () => {
  const React = require('react');
});

@cpojer我想使用__dirname变量,它也是不允许的,我怎样才能得到它?
我不想使用涉及环境的路径,例如/Users/xx/project

@cpojer我不太明白你的解释:

如果你从顶层使用 React,你最终可能会拥有两个 React 副本。

如果我在本地需要 React,我也会有两个本地 React 副本,对吗?

仅当您在两个 require 调用之间调用jest.resetModules()时。

您如何使用 ES6 模块进行这项工作,这些模块不能放在函数范围内?

您可以使用import函数以及例如https://github.com/airbnb/babel-plugin-dynamic-import-node

@SimenB谢谢...你能举个例子吗? 我正在使用支持动态导入的 TypeScript,但我不清楚这将如何工作,因为模拟实现变得异步,Jest 是否知道如何在继续测试用例之前等待模拟解决?

只是等待承诺。

let myDep;

beforeEach(async () => {
  jest.resetModules();

  myDep = await import('./some-modules.js');
})

不知道打字稿看起来如何,但应该不会有太大不同

我在示例中使用 ES6 语法模拟函数时遇到问题:

/**
 * @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);
  });
});

扩展运算符 (...) 和生成器函数被 babel 转换为使用_extendsregeneratorRuntime的东西,因此无法访问:

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.

有没有人遇到过这个问题? 我用最新的笑话。

得到同样的错误,但有:

Invalid variable access: _asyncToGenerator

我正在使用 babel-plugin-transform-regenerator。 在这种情况下,我如何才能不抱怨“ jest.mock()的模块工厂”不被“允许引用任何超出范围的变量”?!

// 编辑:
完整的测试是

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

将测试的某些部分包装在异步 IIFE 中并删除测试函数前面的async使得 jest 不会抛出错误:

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

使用jest.doMock而不是jest.mock对我有帮助。

还不完全确定,因为现在还有其他事情失败了(😄),但看起来确实有帮助,是的。 谢谢! 🙂

知道为什么doMock有效而mock无效吗? 对我来说奇怪的一点是,如果我将变量命名为“MockedComponent”,我会收到一个错误,但是当我输入“mockedComponent”时没有错误,但引用是“未定义”。

'jest.mock' 调用被预处理器从 'it' 调用转移到外部闭包,它不能很好地工作。 'jest.doMock' 调用不受预处理器的影响。

当我用nodejs 10.0.0运行 jest 时遇到了这个问题,只是降级的节点版本就可以了。

@Soontao我无法复制,你能设置一个小复制吗?

@SimenB
感谢您的快速回复,但是当我尝试使用node v10重现该问题时,我发现所有测试都运行良好,我认为问题可能是由其他原因引起的,我在重新安装 nodejs 时丢失了它们。

使用 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)

这与节点 10 没有任何关系,只是我们在白名单中没有console 。 欢迎公关! 我们真的应该只使用一些globals模块而不是手动白名单......

最后一个固定在这里:#6075

yarn add --dev babel-jest babel-core regenerator-runtime升级 babel-jest 为我修复了这个错误。

我只是在谷歌搜索时偶然发现了这一点,似乎我和其他人一样错过了错误消息中的这一关键行:

_如果确保懒惰地需要模拟,则允许以mock前缀的变量名。_

只需更改您要模拟的名称即可模拟YourComponentName

在我的 jest.conf 中添加该代码以在测试中添加 tsx 支持后,我遇到了这个问题(没有该代码,我无法在我的 spec.tsx 文件中编写 tsx:

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

我得到的错误:

   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.

代码本身:

const DEBOUNCE_DELAY = 10
const _debounce = jest.requireActual('lodash-es/debounce').default
jest.mock('lodash-es/debounce', () =>
  jest.fn((fn) => _debounce(fn, DEBOUNCE_DELAY))
)

我不得不用幻数和内联导入重写它:

jest.mock('lodash-es/debounce', () =>
  jest.fn((fn) => jest.requireActual('lodash-es/debounce').default(fn, 10))
)

请注意,如果没有全局变量 ( 'ts-jest': { babelConfig: true } ) 中的配置,代码可以正常工作。 但是,如果没有配置中的那一行,我无法使用 tsx 运行测试,我遇到了这个错误:

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

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

和 babel 配置本身:

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

似乎这样的问题仍然存在,现在即使是变通方法也无助于创建反应应用程序

`
ReferenceError:未定义模拟组件

  17 | const mockComponent = () => <div>Mock</div>;
  18 | 
> 19 | jest.mock('./components/Component', () => ({ Component: mockComponent }));

`

@khryshyn
Jest 会自动将 jest.mock 调用提升到模块的顶部。
这就是为什么 jest.mock 运行时尚未定义mockComponent const 的原因。

为了解决这个“问题/功能”,我分两步完成:

jest.mock('./components/Component', () => ({ Component: jest.fn() }));
import { Component } from "./components/Component";

Component.mockImplementation(() => <div>Mock</div>);

@khryshyn
Jest 会自动将 jest.mock 调用提升到模块的顶部。
这就是为什么 jest.mock 运行时尚未定义mockComponent const 的原因。

为了解决这个“问题/功能”,我分两步完成:

jest.mock('./components/Component', () => ({ Component: jest.fn() }));
import { Component } from "./components/Component";

Component.mockImplementation(() => <div>Mock</div>);

这真的正确吗? 正如上面已经提到的@nckblu ,以 'mock' 开头的变量应该作为例外提供。 'mockComponent' 应该属于那个例外,对吧?

同时,如果您想要一种解决方法来添加调试语句,例如 console.log('Checking...'),请在 console.log 前加上 global 前缀以使其工作。

global.console.log('global console working')

此页面是否有帮助?
0 / 5 - 0 等级