如何模拟一个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(); }); }); 

好吧,但现在getDataFromServerwindow是不可用的(好吧,根本没有window ),我不知道一种方法直接注入到widget.js自己的范围内。

那么我从哪里去呢?

  1. 有没有办法来访问widget.js的范围,或者至less用我自己的代码replace它的导入?
  2. 如果没有,我怎样才能使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); }); });