如何模拟一个ES6模块的import?
我有以下ES6模块:
network.js
export function getDataFromServer() { return ... }
widget.js
import { getDataFromServer } from 'network.js'; export class Widget() { constructor() { getDataFromServer("dataForWidget") .then(data => this.render(data)); } render() { ... } }
我正在寻找一种方法来testing一个模拟的getDataFromServer
实例的Widget。 如果我使用单独的<script>
而不是ES6模块,就像在Karma中那样,我可以编写我的testing:
describe("widget", function() { it("should do stuff", function() { let getDataFromServer = spyOn(window, "getDataFromServer").andReturn("mockData") let widget = new Widget(); expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget"); expect(otherStuff).toHaveHappened(); }); });
但是,如果我在浏览器之外单独testingES6模块(例如使用Mocha + babel),我会写如下内容:
import { Widget } from 'widget.js'; describe("widget", function() { it("should do stuff", function() { let getDataFromServer = spyOn(?????) // How to mock? .andReturn("mockData") let widget = new Widget(); expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget"); expect(otherStuff).toHaveHappened(); }); });
好吧,但现在getDataFromServer
在window
是不可用的(好吧,根本没有window
),我不知道一种方法直接注入到widget.js
自己的范围内。
那么我从哪里去呢?
- 有没有办法来访问
widget.js
的范围,或者至less用我自己的代码replace它的导入? - 如果没有,我怎样才能使
Widget
可testing?
我考虑的东西:
一个。 手动dependency injection。
从widget.js
删除所有导入,并期望调用者提供代码。
export class Widget() { constructor(deps) { deps.getDataFromServer("dataForWidget") .then(data => this.render(data)); } }
我很不舒服地搞乱Widget的公共接口,并暴露实现细节。 不行。
湾 公开import允许嘲笑他们。
就像是:
import { getDataFromServer } from 'network.js'; export let deps = { getDataFromServer }; export class Widget() { constructor() { deps.getDataFromServer("dataForWidget") .then(data => this.render(data)); } }
然后:
import { Widget, deps } from 'widget.js'; describe("widget", function() { it("should do stuff", function() { let getDataFromServer = spyOn(deps.getDataFromServer) // ! .andReturn("mockData"); let widget = new Widget(); expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget"); expect(otherStuff).toHaveHappened(); }); });
这是侵入性较小,但要求我为每个模块编写大量的样板文件,并且仍然存在使用getDataFromServer
而不是deps.getDataFromServer
的风险。 我对此感到不安,但这是我迄今为止最好的想法。
我已经开始在我的testing中使用import * as obj
样式,它将一个模块的所有输出作为一个对象的属性导入,然后可以被模拟。 我发现这比使用rewire或者proxyquire或者其他类似的技术要干净得多。 例如,当我需要模拟Redux动作时,我经常这样做。 以下是我可能用于上面的示例:
import * as network from 'network.js'; describe("widget", function() { it("should do stuff", function() { let getDataFromServer = spyOn(network, "getDataFromServer").andReturn("mockData") let widget = new Widget(); expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget"); expect(otherStuff).toHaveHappened(); }); });
如果你的函数碰巧是默认的导出,那么import * as network from './network'
会产生{default: getDataFromServer}
,你可以模拟network.default。
@carpeliam是正确的,但是请注意,如果你想窥探模块中的函数,并使用该模块中的另一个函数调用该函数,则需要将该函数作为出口名称空间的一部分调用,否则将不会使用间谍。
错误的例子:
// mymodule.js export function myfunc2() {return 2;} export function myfunc1() {return myfunc2();} // tests.js import * as mymodule describe('tests', () => { beforeEach(() => { spyOn(mymodule, 'myfunc2').and.returnValue = 3; }); it('calls myfunc2', () => { let out = mymodule.myfunc1(); // out will still be 2 }); });
正确的例子:
export function myfunc2() {return 2;} export function myfunc1() {return exports.myfunc2();} // tests.js import * as mymodule describe('tests', () => { beforeEach(() => { spyOn(mymodule, 'myfunc2').and.returnValue = 3; }); it('calls myfunc2', () => { let out = mymodule.myfunc1(); // out will be 3 which is what you expect }); });
我发现这个语法正在工作:
我的模块:
// mymod.js import shortid from 'shortid'; const myfunc = () => shortid(); export default myfunc;
我的模块的testing代码:
// mymod.test.js import myfunc from './mymod'; jest.mock('shortid'); import shortid from 'shortid'; describe('mocks shortid', () => { it('works', () => { shortid.mockImplementation(() => 1); expect(myfunc()).toEqual(1); }); });
看文档 。
@ vdloo的答案让我朝着正确的方向前进,但同时使用commonjs“exports”和ES6模块“导出”关键字在同一个文件中并不适用于我(webpack v2抱怨)。 相反,我使用默认(命名variables)导出包装所有单个命名模块导出,然后导入我的testing文件中的默认导出。 我正在使用以下导出设置与摩卡/ sinon和stubbing工作正常,无需rewire等:
// MyModule.js let MyModule; export function myfunc2() { return 2; } export function myfunc1() { return MyModule.myfunc2(); } export default MyModule = { myfunc1, myfunc2 } // tests.js import MyModule from './MyModule' describe('MyModule', () => { const sandbox = sinon.sandbox.create(); beforeEach(() => { sandbox.stub(MyModule, 'myfunc2').returns(4); }); afterEach(() => { sandbox.restore(); }); it('myfunc1 is a proxy for myfunc2', () => { expect(MyModule.myfunc1()).to.eql(4); }); });