Jest: A fábrica de módulos de `jest.mock()` não tem permissão para referenciar nenhuma variável fora do escopo

Criado em 11 jan. 2017  ·  33Comentários  ·  Fonte: facebook/jest

Estou usando o trecho de #1960 para simular Picker no RN

import React, {Component} from 'react';

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

Funciona bem no Jest 17, lança o seguinte erro no 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

Estou usando React 15.4.2 e RN 0.40

Eu tentei babel-jest@test e eles são executados como esperado, mas todos os meus instantâneos falham, parece que mais adereços estão chegando, o que provavelmente não está relacionado a isso.

Qualquer coisa que eu possa fazer para corrigir isso agora ou devo esperar pelo próximo lançamento por babel-jest ?

Obrigado :)

Comentários muito úteis

Usar jest.doMock em vez de jest.mock me ajudou.

Todos 33 comentários

você precisa fazer isso:

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

Isso costumava ser um bug que corrigimos. Em uma simulação, você só pode exigir coisas localmente e não tem permissão para acessar variáveis ​​externas.

Para explicar o porquê: Com jest.resetModules() você pode redefinir todos os módulos atualmente disponíveis, então quando você chamar require, você obterá uma nova versão de cada módulo. Se você usar o React do nível superior, você acabará tendo potencialmente duas cópias do React.

Ah ha, essa é a parte que eu não conseguia entender. Obrigado!

Este uso está ok e há uma escotilha de escape para ele. Chame sua variável mockFoo .

Mas, se eu tiver vários mocks:

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

Eu tenho que colocar const React = require('React'); em cada simulação?

sim.

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

caso alguém copie e cole isso e veja que está falhando no CI (circle/gitlab) e não no local, certifique-se de que React seja uma minúscula react

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

@cpojer Eu quero usar a variável __dirname , também não é permitida, como posso obtê-la?
Eu não quero usar um caminho envolvido no ambiente, como /Users/xx/project

@cpojer Eu realmente não entendo sua explicação:

Se você usar o React do nível superior, você acabará tendo potencialmente duas cópias do React.

Se eu precisar do React localmente, também terei duas cópias do React local, certo?

Somente se você chamar jest.resetModules() entre os dois exigirá chamadas.

Como você faz isso funcionar com módulos ES6, que não podem ser colocados dentro do escopo da função?

você pode usar a função import , junto com, por exemplo, https://github.com/airbnb/babel-plugin-dynamic-import-node

@SimenB Obrigado... você pode dar um exemplo? Estou usando o TypeScript que suporta importações dinâmicas, mas não estou claro como isso funcionaria porque a implementação simulada se torna assíncrona, Jest sabe como aguardar a resolução do simulado antes de continuar com os casos de teste?

Basta aguardar a promessa.

let myDep;

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

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

Não faço ideia de como isso fica com o texto datilografado, mas não deve ser muito diferente

Estou tendo problemas para zombar de uma função usando a sintaxe ES6 dentro de um exemplo:

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

O operador de spread (...) e a função do gerador são transformados por um babel em algo usando _extends e regeneratorRuntime que não podem ser acessados:

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.

Alguém já experimentou o problema antes? Eu uso a última piada.

Obtendo o mesmo erro, mas com:

Invalid variable access: _asyncToGenerator

Estou usando o babel-plugin-transform-regenerator. Como posso fazer com que o jest não reclame sobre "A fábrica de módulos de jest.mock() " não ter "permissão para referenciar quaisquer variáveis ​​fora do escopo" neste caso?!

//editar:
O teste completo é

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

Envolver algumas partes do teste em um IIFE assíncrono e remover o async na frente da função de teste faz com que o jest não gere o erro:

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

Usar jest.doMock em vez de jest.mock me ajudou.

Ainda não tenho certeza, já que há outras coisas falhando agora ( 😄 ), mas parece que realmente ajuda, sim. Obrigado! 🙂

Alguma idéia de por que doMock funciona e mock não? Um pouco estranho para mim também foi que se eu colocasse a variável com o nome "MockedComponent" eu recebia um erro, mas quando eu colocava "mockedComponent" não dava erro, mas a referência era "undefined".

As chamadas 'jest.mock' são movidas das chamadas 'it' para o encerramento externo por um pré-processador e não funciona muito bem. As chamadas 'jest.doMock' não são afetadas por um pré-processador.

Eu encontro esse problema quando executo jest com nodejs 10.0.0 , apenas a versão do nó com downgrade funciona.

@Soontao Não consigo reproduzir isso, você pode configurar uma pequena reprodução?

@SimenB
Obrigado pela sua resposta rápida, mas quando tento reproduzir isso com node v10 , descobri que todos os testes funcionam bem, acho que o problema pode ser causado por outros motivos e os perdi quando reinstalei o nodejs.

Mesmo problema quando executado com 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)

Isso não tem nada a ver com o nó 10, é só que não temos console na whitelist. PR bem-vindo! Nós realmente deveríamos usar algum módulo globals em vez de uma lista de permissões manual...

Último corrigido aqui: #6075

A atualização do babel-jest com yarn add --dev babel-jest babel-core regenerator-runtime corrigiu esse erro para mim.

Acabei de encontrar isso enquanto pesquisava no Google e parece que perdi essa linha crucial na mensagem de erro junto com todos os outros:

_Se for garantido que a simulação é necessária lentamente, nomes de variáveis ​​prefixados com mock são permitidos._

Basta alterar o nome do que você está zombando para zombar de YourComponentName

Eu me deparo com esse problema depois de adicionar esse código no meu jest.conf, para adicionar suporte tsx em testes (sem esse código, não consigo escrever tsx nos meus arquivos 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
    }
  }
}

Erro que recebi:

   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.

Código em si:

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

Eu tive que reescrevê-lo com número mágico e importação inline:

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

Observe que sem essa configuração no código globals ( 'ts-jest': { babelConfig: true } ) funcionou bem. Porém sem essa linha no config não consegui rodar testes com tsx, me deparei com esse erro:

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

Algumas versões do 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",

e a própria configuração do 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 }]
  ]
}

Parece que esse problema ainda existe e agora mesmo as soluções alternativas não ajudam na criação do aplicativo de reação

`
ReferenceError: mockComponent não está definido

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

`

@khryshyn
Jest irá automaticamente elevar chamadas jest.mock para o topo do módulo.
É por isso que seu mockComponent const ainda não está definido quando o jest.mock é executado.

Para contornar esse "problema/recurso", faço isso em 2 etapas:

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

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

@khryshyn
Jest irá automaticamente elevar chamadas jest.mock para o topo do módulo.
É por isso que seu mockComponent const ainda não está definido quando o jest.mock é executado.

Para contornar esse "problema/recurso", faço isso em 2 etapas:

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

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

Isso é realmente correto? Como o @nckblu já mencionou acima, as variáveis ​​que começam com 'mock' devem estar disponíveis como exceção. E 'mockComponent' deve cair nessa exceção, certo?

Enquanto isso, se você quiser uma solução alternativa para adicionar uma instrução de depuração, por exemplo, console.log('Checking...'), prefixe console.log com global para fazê-la funcionar.

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

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

StephanBijzitter picture StephanBijzitter  ·  3Comentários

gustavjf picture gustavjf  ·  3Comentários

mmcgahan picture mmcgahan  ·  3Comentários

stephenlautier picture stephenlautier  ·  3Comentários

ianp picture ianp  ·  3Comentários