如何使用Jest模拟ES6模块导入?
How can I mock an ES6 module import using Jest?
我想测试我的一个ES6模块以特定的方式调用另一个ES6模块。对于Jasmine来说,这非常简单——
应用代码:
// 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等价的是什么?我觉得这是一件很简单的事情,但我一直在绞尽脑汁想弄清楚。
我最接近的是用require
s替换import
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中也能实现。
编辑:几年过去了,这已经不是真正正确的方法了(可能从来都不是,我的错)。
改变导入的模块是很麻烦的,并且可能导致副作用,例如测试通过或失败取决于执行顺序。
出于历史目的,我保留了这个答案的原始形式,但您应该真正使用jest.spyOn
或jest.mock
。参考jest文档或本页上的其他答案了解详细信息。
原答案如下:
我已经能够通过使用涉及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
});
});
您必须模拟模块并自己设置间谍:
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);
});
});
快进到2020年,我发现这篇博客文章是解决方案:Jest mock default和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
还有一件事你需要知道(这花了我一段时间来弄清楚),你不能在测试中调用jest.mock();您必须在模块的顶层调用它。但是,如果您想为不同的测试设置不同的mock,则可以在单个测试中调用mockImplementation()。
使用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);
});
});
其他选项不适合我的情况
为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文件添加到"__ 模拟 __"与文件平行的文件夹dependencies .js。这对我很有效。此外,这还为我提供了从模拟实现返回合适数据的选项。请确保为要模拟的模块提供了正确的路径。
这个问题已经有了答案,但是你可以这样解决:
文件 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
});
});
这里的答案似乎都不适合我(原始函数总是被导入,而不是模拟),而且Jest中对ESM的支持似乎仍在进行中。
在发现这个注释之后,我发现jest.mock()
实际上不能与常规导入一起工作,因为导入总是在mock之前运行(现在也正式记录了这一点)。因此,我使用await import()
导入我的依赖项。这甚至适用于顶级await,所以我只需要调整我的导入:
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);
});
});
我用另一种方法解决了这个问题。假设你有你的dependency.js
export const myFunction = () => { }
我在它旁边创建了一个dependency .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();
})
我尝试了所有的解决方案,没有一个工作或显示大量的TS错误。
我是这样解决的:
format.ts
file:
import camelcaseKeys from 'camelcase-keys'
import parse from 'xml-parser'
class Format {
parseXml (xml: string) {
return camelcaseKeys(parse(xml), {
deep: true,
})
}
}
const format = new Format()
export { format }
format.test.ts
file:
import format from './format'
import camelcaseKeys from 'camelcase-keys'
import parse from 'xml-parser'
jest.mock('xml-parser', () => jest.fn().mockReturnValue('parsed'))
jest.mock('camelcase-keys', () => jest.fn().mockReturnValue('camel cased'))
describe('parseXml', () => {
test('functions called', () => {
const result = format.parseXml('XML')
expect(parse).toHaveBeenCalledWith('XML')
expect(camelcaseKeys).toHaveBeenCalledWith('parsed', { deep: true })
expect(result).toBe('camel cased')
})
})
我对@cam-jackson的原始答案做了一些修改,副作用消失了。我使用lodash库对要测试的对象进行深度克隆,然后对该对象进行任何修改。但是要注意,克隆沉重的对象会对测试性能和测试速度产生负面影响。
objectUndertest.js
const objectUnderTest = {};
export default objectUnderTest;
objectUnderTest.myFunctionUnterTest = () => {
return "this is original function";
};
objectUndertest.test.js
import _ from "lodash";
import objectUndertest from "./objectUndertest.js";
describe("objectUndertest", () => {
let mockObject = objectUndertest;
beforeEach(() => {
mockObject = _.cloneDeep(objectUndertest);
});
test("test function", () => {
mockObject.myFunctionUnterTest = () => {
return "this is mocked function.";
};
expect(mockObject.myFunctionUnterTest()).toBe("this is mocked function.");
});
});
- ES6 (ECMAScript 2015) 模块:导入索引.js
- 如何使用 webpack 要求或仅从模块导入必要的导出
- 将字段从自定义模块导入Odoo(V9)POS模块
- ES6模块导入和依赖关系管理
- 将MQTT NPM模块导入NativeScript
- Typescript:从CommonJS模块导入默认值,从打字文件导出附加类型
- 无法从模块导入函数
- 将模块导入命名空间类
- JavaScript 将一个模块“导入”到另一个模块中
- 当未定义默认导出时,从“模块”导入模块是什么,为什么它与将 * 导入为模块不同
- 避免在ES6 JavaScript web项目中进行深度嵌套模块导入
- 将共享/部分模块导入另一个文件
- 从ES6模块导入到遗留js代码
- ES6破坏和模块导入
- RequireJS:不能将任何模块导入到特定的模块
- 如何使用Jest模拟ES6模块导入?
- 将Angular JavaScript模块导入TypeScript
- 将现有AMD模块导入ES6模块
- 用rollupjs、es2015模块导入
- 从不同的模块导入函数