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


281

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

我想测试我的一个ES6模块以特定方式调用另一个ES6模块。有了茉莉花,这非常容易-

应用程式码:

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

笑话相当于什么?我觉得这是一件很想做的简单的事情,但是我一直在努力尝试弄清头发。

我最接近的方法是将imports 替换为requires,并将其移入测试/函数中。都不是我想要做的事情。

// 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中也有可能。


无论如何,我一直在为这个项目使用Babel,所以我现在不介意继续将imports转换为requires。感谢您的注意。
凸轮杰克逊

如果我拥有ts类A并调用了某些函数,该怎么办,比如说类B的doSomething(),我们该如何模拟,以便类A调用类B函数的模拟版本函数doSomething()
kailash yogeshwar

对于那些想发现这个问题的人,更多github.com/facebook/jest/issues/936
omeralper

Answers:


219

我已经能够通过使用涉及到的黑客解决此问题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
  });
});

正如Mihai Damian在下面正确指出的那样,这是对的模块对象进行了变异dependency,因此它将“泄漏”到其他测试中。因此,如果使用这种方法,则应存储原始值,然后在每次测试后再次将其重新设置。要使用Jest轻松实现此目的,请使用spyOn()方法代替,jest.fn()因为它支持轻松恢复其原始值,因此避免了前面提到的“泄漏”。


感谢分享。我认为最终的结果与此类似-但是,这可能是更清洁- stackoverflow.com/a/38414160/1882064
arcseldon

64
这行得通,但可能不是一个好习惯。测试范围之外的对象更改似乎在测试之间仍然存在。稍后可能会导致其他测试出现意外结果。
米海·达米安

10
除了使用jest.fn(),还可以使用jest.spyOn(),以便稍后可以还原原始方法,因此它不会渗入其他测试。我在这里找到了有关不同方法(jest.fn,jest.mock和jest.spyOn)的不错的文章:medium.com/@rickhanlonii/understanding-jest-mocks-f0046c68e53c
Martinsos

2
请注意:如果与dependency驻留在同一文件中myModule,则它将不起作用。
Lu Tran

2
我认为这不适用于Typescript,因为您要更改的对象是只读的。
adredx

171

您必须模拟模块并自行设置间谍程序:

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

4
这似乎不正确。我得到:babel-plugin-jest-hoist: The second argument of jest.mock must be a function.所以代码甚至都没有编译。
凸轮杰克逊

3
抱歉,我已更新代码。另请注意,其中的路径jest.mock是相对于测试文件的。
AndreasKöberle'16

1
但是,这确实对我有用,但是在使用默认导出时不起作用。
艾里斯·沙弗

4
@IrisSchaffer为了使此工作与您需要添加__esModule: true到模拟对象的默认导出一起使用。这是已编译的代码用于确定它是已编译的es6模块还是commonjs模块的内部标志。
约翰内斯·隆普

24
jest.mock('../dependency', () => ({ default: jest.fn() }))
模拟

50

要使用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);
  });
});

其他选项不适用于我的情况。


6
如果我只想做一个测试,什么是清理此事的最佳方法?里面之后每个?``-afterEach(()=> {jest.unmock(../ dependency');})``
nxmohamad

1
@falsarella在这种情况下doMock确实起作用吗?我遇到了非常类似的问题,当我尝试在特定测试中进行jest.doMock时,它什么也没做,其中整个模块的jest.mock正常工作
Progress1ve

1
@ Progress1ve,您也可以尝试将jest.mock与mockImplementationOnce一起使用
falsarella

1
是的,这是一个正确的建议,但是这要求测试必须是第一个,而且我也不喜欢以这种方式编写测试。我通过导入外部模块并在特定功能上使用spyOn来解决这些问题。
Progress1ve

1
@ Progress1ve嗯,我的意思是将mockImplementationOnce放入每个特定的测试中...无论如何,我很高兴您找到了一个解决方案:)
falsarella

38

为Andreas答案添加更多内容。我在ES6代码中遇到了相同的问题,但不想更改导入内容。看起来很黑。所以我做到了

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 平行的“ __ 模拟 __”文件夹中添加了dependency.js。这对我有用。另外,这给了我选择从模拟实现返回合适的数据的选项。确保为要模拟的模块提供正确的路径。


谢谢你 会尝试一下。喜欢这个解决方案太- stackoverflow.com/a/38414160/1882064
arcseldon

我喜欢这种方法的地方在于,它使您可以为要模拟特定模块的所有场合提供一个手动模拟。例如,我有一个翻译助手,它在很多地方都使用过。该__mocks__/translations.js文件仅默认以以下方式导出jest.fn()export default jest.fn((id) => id)
Iris Schaffer

您还可以jest.genMockFromModule用来从模块生成模拟。facebook.github.io/jest/docs/...
Varunkumar Nagarajan

2
需要注意的一件事是,通过调用`jest.mock('.. dependency')之后,模拟通过的ES6模块export default jest.genMockFromModule('../dependency')将分配其所有功能dependency.default,但其行为与预期的一样。
jhk

7
您的测试断言是什么样的?这似乎是答案的重要组成部分。expect(???)

14

快进到2020年,我发现此链接是解决方案。仅使用ES6模块语法https://remarkablemark.org/blog/2018/06/28/jest-mock-default-named-export/

// 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();您必须在模块的顶层调用它。但是,如果要为不同的测试设置不同的模拟,则可以在各个测试中调用mockImplementation()。


5

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

dependency.js

const doSomething = (x) => x
export default doSomething;

myModule.js:

import doSomething from "./dependency";

export default (x) => doSomething(x * 2);

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

但是“ require”是CommonJS语法-OP询问了ES6模块
Andy

@Andy谢谢您的评论,我更新了答案。顺便说一句,逻辑上也是一样。
Slim

2

我用另一种方法解决了。假设您有dependency.js

export const myFunction = () => { }

除以下内容外,我还创建了一个depdency.mock.js文件:

export const mockFunction = jest.fn();

jest.mock('dependency.js', () => ({ myFunction: mockFunction }));

在测试中,在导入具有缺陷性的文件之前,请先执行以下操作:

import { mockFunction } from 'dependency.mock'
import functionThatCallsDep from './tested-code'

it('my test', () => {
    mockFunction.returnValue(false);

    functionThatCallsDep();

    expect(mockFunction).toHaveBeenCalled();

})
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.