使用 Jest 模拟的服务会导致“ Jest.mock()的模块工厂不允许引用任何范围外的变量”错误

我试图模仿一个服务呼叫,但我挣扎与以下信息: jest.mock()的模块工厂不允许引用任何范围外的变量

我用的是 Babel 和 ES6语法,玩笑和酶。

我有一个名为 Vocabulary的简单组件,它从 vocabularyService获取 VocabularyEntry-Objects 列表并呈现它。

import React from 'react';
import vocabularyService from '../services/vocabularyService';


export default class Vocabulary extends React.Component {
render() {


let rows = vocabularyService.vocabulary.map((v, i) => <tr key={ i } >
<td>{ v.src }</td>
<td>{ v.target }</td>
</tr>);
// render rows
}
}

vocabularyServise非常简单:

import { VocabularyEntry } from '../model/VocabularyEntry';


class VocabularyService {


constructor() {
this.vocabulary = [new VocabularyEntry("a", "b")];
}
}
export default new VocabularyService();

现在我想在测试中嘲笑 vocabularyService:

import { shallow } from 'enzyme';
import React from 'react';
import Vocabulary from "../../../src/components/Vocabulary ";
import { VocabularyEntry } from '../../../src/model/VocabularyEntry'


jest.mock('../../../src/services/vocabularyService', () => ({


vocabulary: [new VocabularyEntry("a", "a1")]


}));


describe("Vocabulary tests", () => {


test("renders the vocabulary", () => {


let $component = shallow(<Vocabulary/>);


// expect something


});
});

运行测试会导致一个错误: babel-plugin-jest-hoist: jest.mock()的模块工厂不允许引用任何范围外的变量。 无效的变量访问: 词汇表条目。

据我所知,我不能使用词汇表条目,因为它没有声明(jest 将模拟定义移动到文件的顶部)。

有人能解释一下我该怎么补救吗?我看到了需要在模拟调用中引用的解决方案,但我不明白如何使用类文件实现这一点。

86738 次浏览

The problem is that all jest.mock will be hoisted to the top of actual code block at compile time, which in this case is the top of the file. At this point VocabularyEntry is not imported. You could either put the mock in a beforeAll block in your test or use jest.mock like this:

import {shallow} from 'enzyme';
import React from 'react';
import Vocabulary from "../../../src/components/Vocabulary ";
import {VocabularyEntry} from '../../../src/model/VocabularyEntry'
import vocabularyService from '../../../src/services/vocabularyService'


jest.mock('../../../src/services/vocabularyService', () => jest.fn())


vocabularyService.mockImplementation(() => ({
vocabulary: [new VocabularyEntry("a", "a1")]
}))

This will first mock the module with a simple spy and after all stuff is imported it sets the real implementation of the mock.

If you are getting similar error when upgrading to newer Jest [19 to 21 in my case], you can try changing jest.mock to jest.doMock.

Found this here – https://github.com/facebook/jest/commit/6a8c7fb874790ded06f4790fdb33d8416a7284c8

You need to store your mocked component in a variable with a name prefixed by "mock". This solution is based on the Note at the end of the error message I was getting.

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.

import {shallow} from 'enzyme';
import React from 'react';
import Vocabulary from "../../../src/components/Vocabulary ";
import {VocabularyEntry} from '../../../src/model/VocabularyEntry'


const mockVocabulary = () => new VocabularyEntry("a", "a1");


jest.mock('../../../src/services/vocabularyService', () => ({
default: mockVocabulary
}));


describe("Vocabulary tests", () => {


test("renders the vocabulary", () => {


let $component = shallow(<Vocabulary/>);


// expect something


});

In my case this issue started after I was upgrade my react-native project to v0.61 using react-native-git-upgrade.

After I have tried everything I could. I decide to clean the project and all my tests back to work.

# react-native-clean-project

However watch out when running the react-native-clean-project, it can wipe out all ios and android folder including native code, so just answer N when prompted. In my case I just selected wipe node_modules folder.

jest.mock("../../../src/services/vocabularyService", () => {
// eslint-disable-next-line global-require
const VocabularyEntry = require("../../../src/model/VocabularyEntry");


return {
vocabulary: [new VocabularyEntry("a", "a1")]
};
});

I think it should work with dynamic imports as well instead of require but didn't manage to make it work.

When implementing a jest.fn() in a module, which was mocked with jest.mock, make sure you do the following two steps:

  1. The variable name should begin with mock.
  2. If you wrote something like coolFunction: mockCoolFunction, make sure to change it to coolFunction: (...args) => mockCoolFunction(...args). This way, Jest doesn’t need to know what mockCoolFunction is, as long as you haven’t run the function.

For further information, I recommend this blog post.