Jest: Фабрике модулей `jest.mock()` не разрешено ссылаться на какие-либо переменные вне области видимости.

Созданный на 11 янв. 2017  ·  33Комментарии  ·  Источник: facebook/jest

Я использую фрагмент из #1960, чтобы издеваться над Picker в RN.

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 (круг/gitlab), а не в своем локальном, убедитесь, что React является строчной буквой react

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

@cpojer Я хочу использовать переменную __dirname , это тоже не разрешено, как мне ее получить?
Я не хочу использовать путь, связанный с окружением, например /Users/xx/project

@cpojer Я не совсем понимаю твое объяснение:

Если вы используете React на верхнем уровне, у вас потенциально может быть две копии React.

Если мне потребуется React локально, у меня также будет две копии локального React, верно?

Только если вы вызываете jest.resetModules() между двумя вызовами require.

Как заставить это работать с модулями 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);
  });
});

Оператор распространения (...) и функция генератора преобразуются вавилоном во что-то, используя _extends и regeneratorRuntime соответственно, к чему нет доступа:

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 перед тестовой функцией, шутка не выдает ошибку:

 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 , работает только пониженная версия узла .

@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

Обновление babel-jest с помощью yarn add --dev babel-jest babel-core regenerator-runtime исправило эту ошибку для меня.

Я только что наткнулся на это во время поиска в Google, и кажется, что я пропустил эту важную строку в сообщении об ошибке вместе со всеми остальными:

_Если гарантируется, что макет требуется лениво, разрешены имена переменных с префиксом mock ._

Просто измените имя того, что вы издеваетесь, чтобы издеваться над YourComponentName

Я столкнулся с этой проблемой после того, как добавил этот код в свой jest.conf, чтобы добавить поддержку tsx в тесты (без этого кода я не могу написать tsx в своих файлах spec.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",

и сам конфиг бабеля:

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: mockComponent не определен

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

`

@хришин
Jest автоматически поднимает вызовы jest.mock в верхнюю часть модуля.
Вот почему ваш mockComponent const еще не определен при запуске jest.mock.

Чтобы обойти эту «проблему/функцию», я делаю это в два этапа:

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

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

@хришин
Jest автоматически поднимает вызовы jest.mock в верхнюю часть модуля.
Вот почему ваш mockComponent const еще не определен при запуске jest.mock.

Чтобы обойти эту «проблему/функцию», я делаю это в два этапа:

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 рейтинги