如何使用 Jest 模拟 ES6 模块导入?
How can I mock an ES6 module import using Jest?
我想测试一下我的 ES6 modules calls another ES6 module in a particular way. With 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
替换 import
,然后将它们移到 tests/functions 中。这两个都不是我想做的。
// 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 中是可能的。
你必须自己模拟模块并设置间谍:
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.spyOn
或 jest.mock
。有关详细信息,请参阅笑话文档或此页面上的其他答案。
原回答如下:
我已经能够通过使用涉及 import *
的 hack 来解决这个问题。它甚至适用于命名导出和默认导出!
对于命名导出:
// 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
});
});
向 添加更多内容。我在使用 ES6 代码时遇到了同样的问题,但我不想改变导入。那看起来很老套。所以我这样做了:
import myModule from '../myModule';
import dependency from '../dependency';
jest.mock('../dependency');
describe('myModule', () => {
it('calls the dependency with double the input', () => {
myModule(2);
});
});
并在与文件 [平行的“__ mocks __”文件夹中添加了文件 dependency.jsdependency.js。这对我有用。此外,这让我可以选择 return 来自模拟实现的合适数据。确保为要模拟的模块提供正确的路径。
使用 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);
});
});
其他选项对我的情况不起作用。
我用另一种方式解决了这个问题。假设您有 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();
})
问题已经回答,但您可以这样解决:
文件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
});
});
快进到 2020 年,我发现这个博客 post 是解决方案: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
还有一件事你需要知道(我花了一段时间才弄明白)是你不能在测试中调用 jest.mock();您必须在模块的顶层调用它。但是,如果您想为不同的测试设置不同的模拟,您可以在单独的测试中调用 mockImplementation()。
None 这里的答案似乎对我有用,Jest 中的 ESM 支持似乎仍然是 work in progress。
在发现 this comment 之后,我发现 jest.mock()
并不真正适用于常规导入,因为导入总是在 mock 之前 运行。因此,我正在使用 async import()
:
导入我的依赖项
import { describe, expect, it, jest } from '@jest/globals';
jest.mock('../dependency', () => ({
doSomething: jest.fn()
}));
describe('myModule', async () => {
const myModule = await import('../myModule');
const dependency = await import('../dependency');
it('calls the dependency with double the input', () => {
myModule(2);
expect(dependency.doSomething).toBeCalledWith(4);
});
});
我想测试一下我的 ES6 modules calls another ES6 module in a particular way. With 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
替换 import
,然后将它们移到 tests/functions 中。这两个都不是我想做的。
// 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 中是可能的。
你必须自己模拟模块并设置间谍:
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.spyOn
或 jest.mock
。有关详细信息,请参阅笑话文档或此页面上的其他答案。
原回答如下:
我已经能够通过使用涉及 import *
的 hack 来解决这个问题。它甚至适用于命名导出和默认导出!
对于命名导出:
// 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');
describe('myModule', () => {
it('calls the dependency with double the input', () => {
myModule(2);
});
});
并在与文件 [平行的“__ mocks __”文件夹中添加了文件 dependency.jsdependency.js。这对我有用。此外,这让我可以选择 return 来自模拟实现的合适数据。确保为要模拟的模块提供正确的路径。
使用 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);
});
});
其他选项对我的情况不起作用。
我用另一种方式解决了这个问题。假设您有 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();
})
问题已经回答,但您可以这样解决:
文件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
});
});
快进到 2020 年,我发现这个博客 post 是解决方案: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
还有一件事你需要知道(我花了一段时间才弄明白)是你不能在测试中调用 jest.mock();您必须在模块的顶层调用它。但是,如果您想为不同的测试设置不同的模拟,您可以在单独的测试中调用 mockImplementation()。
None 这里的答案似乎对我有用,Jest 中的 ESM 支持似乎仍然是 work in progress。
在发现 this comment 之后,我发现 jest.mock()
并不真正适用于常规导入,因为导入总是在 mock 之前 运行。因此,我正在使用 async import()
:
import { describe, expect, it, jest } from '@jest/globals';
jest.mock('../dependency', () => ({
doSomething: jest.fn()
}));
describe('myModule', async () => {
const myModule = await import('../myModule');
const dependency = await import('../dependency');
it('calls the dependency with double the input', () => {
myModule(2);
expect(dependency.doSomething).toBeCalledWith(4);
});
});