Jest: Die Modulfabrik von `jest.mock()` darf keine Variablen außerhalb des Gültigkeitsbereichs referenzieren

Erstellt am 11. Jan. 2017  ·  33Kommentare  ·  Quelle: facebook/jest

Ich verwende das Snippet von #1960, um Picker in RN zu verspotten

import React, {Component} from 'react';

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

Funktioniert gut in Jest 17, wirft folgenden Fehler 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

Ich verwende React 15.4.2 und RN 0.40

Ich habe babel-jest@test ausprobiert und sie laufen wie erwartet, aber alle meine Snapshots schlagen fehl, es sieht so aus, als würden mehr Requisiten durchkommen, was wahrscheinlich nichts damit zu tun hat.

Kann ich jetzt irgendetwas tun, um das Problem zu beheben, oder sollte ich auf die nächste Version für babel-jest warten?

Danke :)

Hilfreichster Kommentar

Die Verwendung jest.doMock anstelle von jest.mock hat mir geholfen.

Alle 33 Kommentare

Sie müssen dies tun:

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

Dies war früher ein Fehler, den wir behoben haben. In einem Mock können Sie Dinge nur lokal anfordern und dürfen nicht auf externe Variablen zugreifen.

Um den Grund zu erklären: Mit jest.resetModules() können Sie alle derzeit verfügbaren Module zurücksetzen, sodass Sie beim Aufrufen von require eine neue Version jedes Moduls erhalten. Wenn Sie React auf der obersten Ebene verwenden, haben Sie möglicherweise zwei Kopien von React.

Ah ha, das ist der Teil, den ich nicht herausfinden konnte. Danke!

Diese eine Verwendung ist ok und es gibt eine Notluke dafür. Nennen Sie Ihre Variable mockFoo .

Aber wenn ich mehrere Mocks habe:

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

Muss ich const React = require('React'); in jeden einzelnen Mock stecken?

Jawohl.

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

Falls jemand dies kopiert und sieht, dass es in CI (Kreis/Gitlab) und nicht in seinem lokalen System fehlschlägt, stellen Sie sicher, dass React ein Kleinbuchstabe react ist

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

@cpojer Ich möchte die Variable __dirname verwenden, es ist auch nicht erlaubt, wie kann ich es bekommen?
Ich möchte keinen umgebungsbeteiligten Pfad wie /Users/xx/project verwenden

@cpojer Ich verstehe deine Erklärung nicht wirklich:

Wenn Sie React auf der obersten Ebene verwenden, haben Sie möglicherweise zwei Kopien von React.

Wenn ich React lokal benötige, habe ich auch zwei Kopien von lokalem React, richtig?

Nur wenn Sie jest.resetModules() zwischen den beiden anrufen, müssen Sie Anrufe tätigen.

Wie funktioniert das mit ES6-Modulen, die nicht in den Funktionsumfang gesteckt werden können?

Sie können die Funktion import zusammen mit zB https://github.com/airbnb/babel-plugin-dynamic-import-node verwenden

@SimenB Danke ... kannst du ein Beispiel geben? Ich verwende TypeScript, das dynamische Importe unterstützt, aber mir ist nicht klar, wie dies funktionieren würde, da die Scheinimplementierung dann asynchron wird. Weiß Jest, wie er auf die Auflösung des Scheins warten muss, bevor er mit den Testfällen fortfährt?

Warte einfach auf das Versprechen.

let myDep;

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

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

Keine Ahnung, wie das mit Typoskript aussieht, sollte aber nicht allzu anders sein

Ich habe Probleme, mich mit einer Funktion zu verspotten, die die ES6-Syntax in einem Beispiel verwendet:

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

Der Spread-Operator (...) und die Generatorfunktion werden von einem Babel mit _extends und regeneratorRuntime entsprechend in etwas umgewandelt, auf das nicht zugegriffen werden kann:

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.

Hat jemand das Problem schon einmal erlebt? Ich benutze den neuesten Scherz.

Den gleichen Fehler bekommen, aber mit:

Invalid variable access: _asyncToGenerator

Ich verwende den babel-plugin-transform-regenerator. Wie kann ich Scherz dazu bringen, mich nicht darüber zu beschweren, dass „die Modulfabrik von jest.mock() “ in diesem Fall „keine Variablen außerhalb des Geltungsbereichs referenzieren darf“?!

// bearbeiten:
Vollständiger Test ist

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

Wenn Sie einige Teile des Tests in ein asynchrones IIFE einpacken und das async vor der Testfunktion entfernen, wird der Fehler im Scherz nicht ausgegeben:

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

Die Verwendung jest.doMock anstelle von jest.mock hat mir geholfen.

Noch nicht ganz sicher, da jetzt andere Dinge versagen ( 😄 ), aber es sieht so aus, als ob es wirklich hilft, ja. Danke! 🙂

Irgendeine Idee, warum doMock funktioniert und mock nicht? Seltsam für mich war auch, dass ich einen Fehler erhielt, wenn ich die Variable mit dem Namen "MockedComponent" einfügte, aber als ich "mockedComponent" einfügte, gab es keinen Fehler, aber die Referenz war "undefiniert".

Die 'jest.mock'-Aufrufe werden von einem Präprozessor von 'it'-Aufrufen zum äußeren Abschluss verschoben, und es funktioniert nicht sehr gut. „jest.doMock“-Aufrufe werden nicht von einem Präprozessor beeinflusst.

Ich treffe auf dieses Problem, wenn ich Scherz mit nodejs 10.0.0 ausführe, nur die heruntergestufte Knotenversion funktioniert.

@Soontao Ich kann das nicht reproduzieren, kannst du eine kleine Reproduktion einrichten?

@SimenB
Vielen Dank für Ihre schnelle Antwort, aber wenn ich versuche, das mit node v10 zu reproduzieren, habe ich festgestellt, dass alle Tests einwandfrei funktionieren. Ich denke, das Problem kann andere Gründe haben, und ich habe sie verloren, als ich nodejs neu installiert habe.

Gleiches Problem bei Ausführung mit 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)

Das hat nichts mit Node 10 zu tun, es ist nur so, dass wir console nicht in der Whitelist haben. PR willkommen! Wir sollten wirklich einfach ein globals Modul anstelle einer manuellen Whitelist verwenden ...

Zuletzt hier behoben: #6075

Das Upgrade von babel-jest mit yarn add --dev babel-jest babel-core regenerator-runtime hat diesen Fehler für mich behoben.

Ich bin gerade beim Googeln darauf gestoßen und es scheint, als hätte ich diese entscheidende Zeile in der Fehlermeldung zusammen mit allen anderen verpasst:

_Wenn sichergestellt ist, dass der Mock träge benötigt wird, sind Variablennamen mit vorangestelltem mock erlaubt._

Ändern Sie einfach den Namen dessen, was Sie verspotten, um YourComponentName zu verspotten

Ich stoße auf dieses Problem, nachdem ich diesen Code in meiner jest.conf hinzugefügt habe, um tsx-Unterstützung in Tests hinzuzufügen (ohne diesen Code kann ich tsx nicht in meine spec.tsx-Dateien schreiben:

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

Fehler, den ich bekam:

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

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

Ich musste es mit magischer Zahl und Inline-Import umschreiben:

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

Beachten Sie, dass der Code ohne diese Konfiguration in globals ( 'ts-jest': { babelConfig: true } ) gut funktioniert hat. Ohne diese Zeile in der Konfiguration konnte ich jedoch keine Tests mit tsx durchführen, ich wurde mit diesem Fehler konfrontiert:

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

Einige Versionen von 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",

und die babel-config selbst:

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

Scheint, als ob ein solches Problem immer noch besteht, und jetzt helfen nicht einmal Problemumgehungen beim Erstellen einer Reaktions-App-Anwendung

`
ReferenceError: mockComponent ist nicht definiert

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

`

@chryshyn
Jest hebt jest.mock-Aufrufe automatisch an die Spitze des Moduls.
Deshalb ist Ihre mockComponent Konstante noch nicht definiert, wenn jest.mock läuft.

Um dieses "Problem/Feature" zu umgehen, mache ich es in zwei Schritten:

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

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

@chryshyn
Jest hebt jest.mock-Aufrufe automatisch an die Spitze des Moduls.
Deshalb ist Ihre mockComponent Konstante noch nicht definiert, wenn jest.mock läuft.

Um dieses "Problem/Feature" zu umgehen, mache ich es in zwei Schritten:

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

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

Ist das wirklich richtig? Wie bei @nckblu oben bereits erwähnt, sollten Variablen, die mit 'mock' beginnen, ausnahmsweise zur Verfügung stehen. Und 'mockComponent' sollte in diese Ausnahme fallen, richtig?

Wenn Sie in der Zwischenzeit eine Problemumgehung wünschen, um eine Debug-Anweisung hinzuzufügen, z. B. console.log('Checking...'), stellen Sie console.log das Präfix global voran, damit es funktioniert.

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

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen