Jest: La fabrique de modules de `jest.mock()` n'est pas autorisée à référencer des variables hors de portée

Créé le 11 janv. 2017  ·  33Commentaires  ·  Source: facebook/jest

J'utilise l'extrait de # 1960 pour simuler Picker dans RN

import React, {Component} from 'react';

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

Fonctionne bien dans Jest 17, génère l'erreur suivante dans 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

J'utilise React 15.4.2 et RN 0.40

J'ai essayé babel-jest@test et ils fonctionnent comme prévu, mais tous mes instantanés échouent, on dirait que plus d'accessoires arrivent, ce qui n'est probablement pas lié à cela.

Tout ce que je peux faire pour résoudre ce problème maintenant ou dois-je attendre la prochaine version pour babel-jest ?

Merci :)

Commentaire le plus utile

Utiliser jest.doMock au lieu de jest.mock m'a aidé.

Tous les 33 commentaires

tu dois faire ceci:

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

C'était un bogue que nous avons corrigé. Dans une simulation, vous ne pouvez exiger des choses que localement et vous n'êtes pas autorisé à accéder aux variables externes.

Pour expliquer pourquoi : avec jest.resetModules() vous pouvez réinitialiser tous les modules actuellement disponibles, ainsi lorsque vous appelez require, vous obtiendrez une nouvelle version de chaque module. Si vous utilisez React à partir du niveau supérieur, vous finirez par avoir potentiellement deux copies de React.

Ah ha, c'est le peu que je ne pouvais pas suss. Merci!

Cette seule utilisation est correcte et il y a une trappe d'évacuation pour cela. Appelez votre variable mockFoo .

Mais, si j'ai plusieurs mocks:

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

Dois-je mettre const React = require('React'); dans chaque maquette ?

Oui.

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

au cas où quelqu'un copierait coller ceci et le verrait échouer dans CI (circle/gitlab) et non dans leur local, assurez-vous que React est une minuscule react

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

@cpojer Je veux utiliser la variable __dirname , ce n'est pas non plus autorisé, comment puis-je l'obtenir ?
Je ne veux pas utiliser un chemin impliqué dans l'environnement, comme /Users/xx/project

@cpojer Je ne comprends pas vraiment votre explication:

Si vous utilisez React à partir du niveau supérieur, vous finirez par avoir potentiellement deux copies de React.

Si j'ai besoin de React localement, j'aurai également deux copies de React local, n'est-ce pas ?

Uniquement si vous appelez jest.resetModules() entre les deux appels requis.

Comment faites-vous pour que cela fonctionne avec les modules ES6, qui ne peuvent pas être placés dans la portée de la fonction ?

vous pouvez utiliser la fonction import , avec par exemple https://github.com/airbnb/babel-plugin-dynamic-import-node

@SimenB Merci... pouvez-vous donner un exemple ? J'utilise TypeScript qui prend en charge les importations dynamiques, mais je ne sais pas comment cela fonctionnerait, car l'implémentation fictive devient alors asynchrone. Jest sait-il attendre que la simulation soit résolue avant de continuer avec les cas de test ?

Attendez juste la promesse.

let myDep;

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

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

Aucune idée de ce à quoi cela ressemble avec le texte dactylographié, mais ne devrait pas être trop différent

J'ai du mal à me moquer d'une fonction utilisant la syntaxe ES6 dans un exemple :

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

L'opérateur de propagation (...) et la fonction de générateur sont transformés par un babel en quelque chose utilisant _extends et regeneratorRuntime en conséquence qui ne sont pas accessibles :

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.

Quelqu'un a-t-il déjà rencontré le problème ? J'utilise la dernière plaisanterie.

Obtenir la même erreur mais avec:

Invalid variable access: _asyncToGenerator

J'utilise le babel-plugin-transform-regenerator. Comment puis-je me moquer de ne pas me plaindre du fait que "l'usine de modules de jest.mock() " n'est pas "autorisée à référencer des variables hors de portée" dans ce cas ? !

// Éditer:
Le test complet est

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

Envelopper certaines parties du test dans un IIFE asynchrone et supprimer le async devant la fonction de test permet de ne pas lancer l'erreur :

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

Utiliser jest.doMock au lieu de jest.mock m'a aidé.

Pas encore tout à fait sûr car il y a d'autres choses qui échouent maintenant ( 😄 ) mais on dirait que ça aide vraiment, oui. Merci! 🙂

Une idée pourquoi doMock fonctionne et mock ne fonctionne pas ? Bizarre pour moi, c'était aussi que si je mettais la variable avec le nom "MockedComponent", je recevais une erreur, mais quand je mettais "mockedComponent", il n'y avait pas d'erreur, mais la référence était "indéfinie".

Les appels 'jest.mock' sont déplacés des appels 'it' vers la fermeture externe par un préprocesseur et cela ne fonctionne pas très bien. Les appels 'jest.doMock' ne sont pas affectés par un préprocesseur.

Je rencontre ce problème lorsque je lance jest avec nodejs 10.0.0 , juste la version de nœud rétrogradée fonctionne.

@Soontao Je ne peux pas reproduire ça, es-tu capable de mettre en place une petite reproduction ?

@SimenB
Merci pour votre réponse rapide, mais lorsque j'essaie de reproduire cela avec node v10 , j'ai constaté que tous les tests fonctionnent correctement, je pense que le problème peut être causé par d'autres raisons, et je les ai perdus lorsque j'ai réinstallé nodejs.

Même problème lors de l'exécution avec 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)

Cela n'a rien à voir avec le nœud 10, c'est juste que nous n'avons pas console dans la liste blanche. RP bienvenue ! Nous devrions vraiment utiliser un module globals au lieu d'une liste blanche manuelle...

Dernier corrigé ici : #6075

La mise à niveau de babel-jest avec yarn add --dev babel-jest babel-core regenerator-runtime a corrigé cette erreur pour moi.

Je viens de tomber dessus en cherchant sur Google et il semble que j'ai raté cette ligne cruciale dans le message d'erreur avec tout le monde:

_S'il est garanti que la simulation est requise paresseusement, les noms de variables préfixés par mock sont autorisés._

Changez simplement le nom de ce dont vous vous moquez pour vous moquer de YourComponentName

Je rencontre ce problème après avoir ajouté ce code dans mon jest.conf, pour ajouter le support tsx dans les tests (sans ce code, je ne peux pas écrire tsx dans mes fichiers 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
    }
  }
}

Erreur j'ai eu :

   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 lui-même :

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

J'ai dû le réécrire avec un nombre magique et une importation en ligne :

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

Notez que sans cette configuration dans globals ( 'ts-jest': { babelConfig: true } ) le code a bien fonctionné. Cependant, sans cette ligne dans la configuration, je n'ai pas pu exécuter de tests avec tsx, j'ai rencontré cette erreur :

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

Certaines versions de 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",

et la configuration babel elle-même :

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

On dirait qu'un tel problème existe toujours et maintenant même les solutions de contournement n'aident pas à créer une application d'application de réaction

`
ReferenceError : mockComponent n'est pas défini

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

`

@khryshyn
Jest hissera automatiquement les appels jest.mock en haut du module.
C'est pourquoi votre constante mockComponent n'est pas encore définie lorsque jest.mock s'exécute.

Pour contourner ce "problème/fonctionnalité", je le fais en 2 étapes ainsi :

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

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

@khryshyn
Jest hissera automatiquement les appels jest.mock en haut du module.
C'est pourquoi votre constante mockComponent n'est pas encore définie lorsque jest.mock s'exécute.

Pour contourner ce "problème/fonctionnalité", je le fais en 2 étapes ainsi :

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

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

Est-ce vraiment correct ? Comme @nckblu l'a déjà mentionné ci-dessus, les variables commençant par 'mock' devraient être disponibles à titre exceptionnel. Et 'mockComponent' devrait tomber dans cette exception, n'est-ce pas?

En attendant, si vous souhaitez une solution de contournement pour ajouter une instruction de débogage, par exemple console.log('Checking...'), préfixez console.log avec global pour que cela fonctionne.

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

Cette page vous a été utile?
0 / 5 - 0 notes