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

Cam*_*son 227 javascript mocking node.js ecmascript-6 jestjs

我开始认为这是不可能的,但无论如何我想问.

我想测试我的一个ES6模块以特定方式调用另一个ES6模块.使用Jasmine这非常容易 -

应用代码:

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

export default (x) => {
  dependency.doSomething(x * 2);
}
Run Code Online (Sandbox Code Playgroud)

和测试代码:

//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);
  });
});
Run Code Online (Sandbox Code Playgroud)

什么与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);
  });
});
Run Code Online (Sandbox Code Playgroud)

对于奖励积分,当内部函数dependency.js是默认导出时,我很乐意让整个过程完成.但是,我知道监视默认导出在Jasmine中不起作用(或者至少我无法让它工作),所以我并不是希望Jest中也可以.

Cam*_*son 187

我已经能够通过使用涉及的黑客来解决这个问题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);
  });
});
Run Code Online (Sandbox Code Playgroud)

或者对于默认导出:

// 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
  });
});
Run Code Online (Sandbox Code Playgroud)

正如Mihai Damian在下面正确地指出的那样,这正在改变模块对象dependency,因此它将"泄漏"到其他测试中.因此,如果您使用此方法,则应存储原始值,然后在每次测试后重新设置.要使用Jest轻松完成此操作,请使用spyOn()方法,而不是jest.fn()因为它支持轻松恢复其原始值,因此避免在提到"泄漏"之前.

  • 这有效,但这可能不是一个好习惯.对测试范围之外的对象的更改似乎在测试之间保持不变.这可以在以后导致其他测试中的意外结果. (58认同)
  • 我认为这不适用于 Typescript,因为你正在改变的对象是只读的。 (23认同)
  • 您可以使用jest.spyOn()代替使用jest.fn(),以便稍后可以恢复原始方法,因此它不会流入其他测试.我在这里找到了关于不同方法的好文章(jest.fn,jest.mock和jest.spyOn):https://medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c. (8认同)
  • 不适合我.模拟失败了 (5认同)
  • 请注意:如果`dependency`与`myModule`驻留在同一文件中,它将不起作用。 (2认同)
  • 这不适用于在 package.json 中使用“type: module”打开的节点实验模块。我让它与 babel 转译器一起工作。 (2认同)

And*_*rle 140

你必须自己模拟模块并设置间谍:

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);
  });
});
Run Code Online (Sandbox Code Playgroud)

  • 模拟默认导出:```jest.mock('../ dependency',()=>({default:jest.fn()}))``` (19认同)
  • 这似乎不对.我得到:`babel-plugin-jest-hoist:jest.mock的第二个参数必须是一个函数.所以代码甚至没有编译. (3认同)
  • 对不起,我已经更新了我的代码.还请注意`jest.mock`中的路径是相对于测试文件的. (3认同)
  • @IrisSchaffer为了使用默认导出工作,你需要将`__esModule:true`添加到模拟对象.这是转换后的代码用来确定它是一个转换的es6模块还是一个commonjs模块的内部标志. (3认同)

And*_*ndy 75

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

仅使用 ES6 模块语法:

// 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
Run Code Online (Sandbox Code Playgroud)

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

  • 帮助我让它工作的关键是“你不能在测试中调用 jest.mock() ;你必须在模块的顶层调用它” (9认同)
  • 您必须在测试顶部放置“jest.mock”的原因是,jest 在内部会在导入之前重新排序“jest.mock”。这就是为什么你的“jest.mock”是在导入之前还是之后并不重要。如果将其放在函数体中,它将无法正常运行。 (2认同)

fal*_*lla 44

使用jest模拟ES6依赖模块的默认导出:

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);
  });
});
Run Code Online (Sandbox Code Playgroud)

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

  • 如果我只想进行一次测试,那么清理它的最佳方法是什么?在每个里面?````afterEach(()=> {jest.unmock(../ dependency');})```` (6认同)

mds*_*ubi 38

添加更多安德烈斯答案.我有与ES6代码相同的问题,但不想改变导入.看起来很丑陋.所以我这样做了

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

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
  });
});
Run Code Online (Sandbox Code Playgroud)

并在依赖于dependency.js的"__ mocks __"文件夹中添加了dependency.js.这对我有用.此外,这使我可以选择从模拟实现中返回合适的数据.确保为要模拟的模块提供正确的路径.

  • 你的测试断言是什么样的?这似乎是答案的重要部分.`期望(???)` (6认同)
  • 有一点需要注意的是,通过`export default jest.genMockFromModule('../ dependency')`模拟的ES6模块将在调用`jest.mock('.. dependency'后将所有函数分配给`dependency.default` ),但以其他方式表现如预期. (2认同)

cda*_*uth 20

这里的答案似乎都不适合我(始终导入原始函数而不是模拟函数),并且 Jest 中的 ESM 支持似乎仍在进行中

发现此评论后,我发现这jest.mock()实际上不适用于常规导入,因为导入始终在模拟之前运行(这现在也已正式记录)。因此,我使用 导入我的依赖项await import()。这甚至可以与顶级等待一起使用,所以我只需要调整我的导入:

import { describe, expect, it, jest } from '@jest/globals';

jest.unstable_mockModule('../dependency', () => ({
  doSomething: jest.fn()
}));

const myModule = await import('../myModule');
const dependency = await import('../dependency');

describe('myModule', async () => {
  it('calls the dependency with double the input', () => {
    myModule(2);
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});
Run Code Online (Sandbox Code Playgroud)


Sli*_*lim 11

问题已经回答了,但您可以这样解决:

文件依赖.js

const doSomething = (x) => x
export default doSomething;
Run Code Online (Sandbox Code Playgroud)

文件myModule.js

import doSomething from "./dependency";

export default (x) => doSomething(x * 2);
Run Code Online (Sandbox Code Playgroud)

文件myModule.spec.js

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

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    doSomething.mockImplementation((x) => x * 10)

    myModule(2);

    expect(doSomething).toHaveBeenCalledWith(4);
    console.log(myModule(2)) // 40
  });
});
Run Code Online (Sandbox Code Playgroud)

  • 但“require”是 CommonJS 语法 - OP 正在询问 ES6 模块 (2认同)
  • 在模拟`doSomething`之前如何调用`.mockImplementation`? (2认同)