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