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

Posted

技术标签:

【中文标题】使用 Jest 模拟的服务导致“不允许 jest.mock() 的模块工厂引用任何超出范围的变量”错误【英文标题】:Service mocked with Jest causes "The module factory of jest.mock() is not allowed to reference any out-of-scope variables" error 【发布时间】:2017-11-22 19:11:13 【问题描述】:

我正在尝试模拟对服务的调用,但遇到以下消息:jest.mock() 的模块工厂不允许引用任何范围外的变量 .

我正在使用带有 ES6 语法、玩笑和酶的 babel。

我有一个名为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

);

);

运行测试导致错误:Vocabulary.spec.js: babel-plugin-jest-hoist: The module factory of jest.mock() is not allowed to reference any out-of-scope variables。 无效的变量访问:VocabularyEntry。

据我了解,我不能使用 VocabularyEntry,因为它没有声明(因为 jest 将模拟定义移动到文件顶部)。

谁能解释我如何解决这个问题?我看到了需要在模拟调用中引用的解决方案,但我不明白如何使用类文件来做到这一点。

【问题讨论】:

【参考方案1】:

问题是所有jest.mock 在编译时都会被提升到实际代码块的顶部,在这种情况下是文件的顶部。此时VocabularyEntry 没有被导入。您可以将mock 放在测试中的beforeAll 块中,也可以像这样使用jest.mock

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")]
))

这将首先使用一个简单的 spy 模拟模块,然后在导入所有内容后设置模拟的真正实现。

【讨论】:

这似乎是个好办法!我试过这个,但当Vocabulary 尝试调用vocabularyService.vocabulary.map 时会导致错误,因为vocabularyService.vocabulary 未定义。我注销了vocabularyService,它是一个开玩笑的模拟功能。也许这就是问题所在? 啊抱歉,我的代码中有一个错误,mockImplementation 需要返回一个函数而不仅仅是对象。 不适合我 :( 请参阅 ***.com/questions/45771844/…. 当我这样做时,我得到myService.mockImplementation is not a function 这根本不起作用。将jest.mock 移动到beforeAll 会产生相同的错误,使用mockImplementation 会产生TypeError: mockedModule.mockedFunction is not a function【参考方案2】:

如果您在升级到较新的 Jest [在我的情况下为 19 到 21] 时遇到类似错误,您可以尝试将 jest.mock 更改为 jest.doMock

在这里找到这个——https://github.com/facebook/jest/commit/6a8c7fb874790ded06f4790fdb33d8416a7284c8

【讨论】:

【参考方案3】:

您需要将您的模拟组件存储在一个名称以“mock”为前缀的变量中。 此解决方案基于我收到的错误消息末尾的注释。

注意:这是防止未初始化的模拟的预防措施 变量。如果确保懒惰地需要模拟,变量 允许以mock 为前缀的名称。

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

);

【讨论】:

这会产生错误:Cannot access 'mockVocabulary' before initialization【参考方案4】:

就我而言,这个问题是在我使用 react-native-git-upgrade 将我的 react-native 项目升级到 v0.61 之后开始的。

在我尽我所能尝试之后。我决定清理项目并让我的所有测试重新开始工作。

# react-native-clean-project

但是在运行 react-native-clean-project 时要注意,它会清除所有的 iosandroid 文件夹,包括原生代码,所以在提示时回答 N。就我而言,我刚刚选择了擦除 node_modules 文件夹。

【讨论】:

【参考方案5】:
jest.mock("../../../src/services/vocabularyService", () => 
  // eslint-disable-next-line global-require
  const VocabularyEntry = require("../../../src/model/VocabularyEntry");

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

我认为它也应该适用于动态导入,而不是 require,但没能成功。

【讨论】:

以上是关于使用 Jest 模拟的服务导致“不允许 jest.mock() 的模块工厂引用任何超出范围的变量”错误的主要内容,如果未能解决你的问题,请参考以下文章

在 jest-react 中使用 MockedProvider 会导致错误“查询没有更多的模拟响应”

使用 Jest 模拟 Es6 类

无法使用 nuxt 和 jest 模拟服务

使用 Jest 模拟带有 props 的 React 组件

如何使用 Jest 模拟封装在服务类中的 winston 记录器实例

模拟安装挂钩 Jest 测试单元