如何使用 Jest 模拟 ES6 module导入?

IT技术 javascript node.js mocking ecmascript-6 jestjs
2021-02-04 11:25:35

我想测试我的一个ES6module调用特定方式的另一个ES6module。有了茉莉花,这非常容易——

应用代码:

// myModule.js
import dependency from './dependency';

export default (x) => {
  dependency.doSomething(x * 2);
}

和测试代码:

//myModule-test.js
import myModule from '../myModule';
import dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    spyOn(dependency, 'doSomething');

    myModule(2);

    expect(dependency.doSomething).toHaveBeenCalledWith(4);
  });
});

Jest 的等价物是什么?我觉得这是一件很简单的事情,但我一直在努力弄清楚。

我最接近的import是用requires替换s,并将它们移动到测试/函数中。两者都不是我想做的事情。

// myModule.js
export default (x) => {
  const dependency = require('./dependency'); // Yuck
  dependency.doSomething(x * 2);
}

//myModule-test.js
describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    jest.mock('../dependency');

    myModule(2);

    const dependency = require('../dependency'); // Also yuck
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

对于奖励积分,当里面的函数dependency.js是默认导出时,我很乐意让整个事情正常工作但是,我知道监视默认导出在 Jasmine 中不起作用(或者至少我永远无法让它工作),所以我也不希望在 Jest 中实现它。

6个回答

编辑:几年过去了,这不再是真正正确的方法(而且可能从来都不是,我的错)。

对导入的module进行变异是令人讨厌的,并且会导致副作用,例如根据执行顺序通过或失败的测试。

出于历史目的,我将保留此答案的原始形式,但您确实应该使用jest.spyOnor jest.mock有关详细信息,请参阅本页上的笑话文档或其他答案。

原答案如下:


我已经能够通过使用涉及import *. 它甚至适用于命名和默认导出!

对于命名导出:

// dependency.js
export const doSomething = (y) => console.log(y)
// myModule.js
import { doSomething } from './dependency';

export default (x) => {
  doSomething(x * 2);
}
// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    dependency.doSomething = jest.fn(); // Mutate the named export

    myModule(2);

    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

或者对于默认导出:

// dependency.js
export default (y) => console.log(y)
// myModule.js
import dependency from './dependency'; // Note lack of curlies

export default (x) => {
  dependency(x * 2);
}
// myModule-test.js
import myModule from '../myModule';
import * as dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    dependency.default = jest.fn(); // Mutate the default export

    myModule(2);

    expect(dependency.default).toBeCalledWith(4); // Assert against the default
  });
});

我认为这不适用于 Typescript,您正在变异的对象是只读的。
2021-03-15 11:25:35
这不适用于在 package.json 中打开的节点实验moduletype: module我让它与 babel 转译器一起工作。
2021-03-28 11:25:35
您可以使用 jest.spyOn() 而不是使用 jest.fn(),这样您就可以稍后恢复原始方法,这样它就不会渗透到其他测试中。我在这里找到了关于不同方法的好文章(jest.fn、jest.mock 和 jest.spyOn):medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c
2021-03-31 11:25:35
这有效,但这可能不是一个好习惯。对测试范围之外的对象的更改似乎在测试之间持续存在。这可能会在以后导致其他测试中出现意外结果。
2021-04-03 11:25:35
请注意:如果 与dependency位于同一文件中myModule,则它将不起作用。
2021-04-09 11:25:35

您必须模拟module并自己设置间谍:

import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency', () => ({
  doSomething: jest.fn()
}))

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});
模拟默认导出: jest.mock('../dependency', () => ({ default: jest.fn() }))
2021-03-12 11:25:35
@IrisSchaffer 为了使这项工作与您需要添加__esModule: true到模拟对象的默认导出一起工作这是被转译的代码用来确定它是转译的 es6 module还是 commonjs module的内部标志。
2021-03-23 11:25:35
但是,这确实对我有用,但在使用默认导出时无效。
2021-03-30 11:25:35
这似乎不对。我得到:babel-plugin-jest-hoist: The second argument of jest.mock must be a function.所以代码甚至没有编译。
2021-04-05 11:25:35
对不起,我已经更新了我的代码。另请注意,in 中的路径jest.mock是相对于测试文件的。
2021-04-05 11:25:35

快进到 2020 年,我发现这篇博文是解决方案:Jest mock default and named export

仅使用 ES6 module语法:

// esModule.js
export default 'defaultExport';
export const namedExport = () => {};

// esModule.test.js
jest.mock('./esModule', () => ({
  __esModule: true, // this property makes it work
  default: 'mockedDefaultExport',
  namedExport: jest.fn(),
}));

import defaultExport, { namedExport } from './esModule';
defaultExport; // 'mockedDefaultExport'
namedExport; // mock function

还有一件事你需要知道(我花了一段时间才弄清楚)是你不能在测试中调用 jest.mock() ;您必须在module的顶层调用它。但是,如果您想为不同的测试设置不同的模拟,您可以在单个测试中调用 mockImplementation()。

我不清楚'mockedDefaultExport'应该是什么——为什么它不是一个像mockFunctionvs 字符串这样的变量'mockFunction'为什么不让他们两个jest.fn()
2021-03-12 11:25:35
帮助我让它工作的关键是“你不能在测试中调用 jest.mock();你必须在module的顶层调用它”
2021-03-16 11:25:35
__esModule: true使它在我需要模拟默认导出的地方工作。否则它运行良好没有。谢谢你的回答!
2021-03-22 11:25:35
你必须jest.mock在测试顶部的原因是内部 jest 会jest.mock在导入之前重新排序这就是为什么jest.mock您在导入之前或之后并不重要的原因将其放置在函数体中,它将无法正常运行。
2021-03-25 11:25:35
@jcollum 我认为这只是说明任何导出(包括默认导出)都可以是字符串,就像它可以是函数一样容易,并且可以以相同的方式模拟
2021-03-29 11:25:35

使用 Jest 模拟 ES6 依赖module默认导出:

import myModule from '../myModule';
import dependency from '../dependency';

jest.mock('../dependency');

// If necessary, you can place a mock implementation like this:
dependency.mockImplementation(() => 42);

describe('myModule', () => {
  it('calls the dependency once with double the input', () => {
    myModule(2);

    expect(dependency).toHaveBeenCalledTimes(1);
    expect(dependency).toHaveBeenCalledWith(4);
  });
});

其他选项对我的情况不起作用。

@falsarella doMock 在那种情况下真的有效吗?我遇到了非常相似的问题,当我尝试在特定测试中 jest.doMock 时它什么也没做,整个module的 jest.mock 工作正常
2021-03-16 11:25:35
@Progress1ve 嗯,我打算将 mockImplementationOnce 放在每个特定测试中......无论如何,我很高兴你找到了解决方案:)
2021-03-20 11:25:35
是的,这是一个有效的建议,但是这要求测试是第一个,而且我不喜欢以这种方式编写测试。我通过导入外部module并在特定功能上使用 spyOn 来解决这些问题。
2021-03-31 11:25:35
如果我只想进行一次测试,最好的清理方法是什么?里面afterEach?```` afterEach(() => { jest.unmock(../dependency'); }) ````
2021-04-01 11:25:35
@Progress1ve 您也可以尝试将 jest.mock 与 mockImplementationOnce 一起使用
2021-04-10 11:25:35

Andreas 的回答添加更多内容我对 ES6 代码有同样的问题,但我不想改变导入。那看起来很hacky。所以我这样做了:

import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency');

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
  });
});

与文件dependency.js平行的“ __ mocks __”文件夹中添加了文件dependency.js这对我有用。此外,这让我可以选择从模拟实现中返回合适的数据。确保为要模拟的module提供正确的路径。

您还可以使用jest.genMockFromModule从module生成模拟。facebook.github.io/jest/docs/...
2021-03-19 11:25:35
需要注意的一件事是,在调用 `jest.mock('..dependency') 之后,通过模拟的ES6 moduleexport default jest.genMockFromModule('../dependency')将分配给它们的所有功能dependency.default,但其他方面的行为都符合预期。
2021-03-24 11:25:35
你的测试断言是什么样的?这似乎是答案的重要部分。expect(???)
2021-04-06 11:25:35
谢谢你。会试一试。也喜欢这个解决方案 - stackoverflow.com/a/38414160/1882064
2021-04-09 11:25:35
我喜欢这种方法的地方在于,它让您可以为所有需要模拟特定module的场合提供一个手动模拟。比如我有一个翻译助手,很多地方都用到了。__mocks__/translations.js文件只是默认导出jest.fn()如下:export default jest.fn((id) => id)
2021-04-09 11:25:35