[javascript] 如何模擬ES6模塊的導入?


Answers

@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
    });
});
Question

我有以下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() {
    ...
  }
}

我正在尋找一種使用getDataFromServer的模擬實例來測試Widget的方法。 如果我使用單獨的<script>代替ES6模塊,就像在Karma中一樣,我可以編寫我的測試:

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();
  });
});

但是,如果我在瀏覽器外單獨測試ES6模塊(如使用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不可用( widget.js ,根本沒有window ),我不知道一種方法直接將東西注入到widget.js自己的作用域中。

那麼我從哪裡去?

  1. 有沒有辦法訪問widget.js的作用域,或者至少用我自己的代碼替換它的導入?
  2. 如果沒有,我怎樣才能讓Widget可測試的?

我認為的東西:

一個。 手動依賴注入。

widget.js刪除所有導入,並期望調用者提供代碼。

export class Widget() {
  constructor(deps) {
    deps.getDataFromServer("dataForWidget")
    .then(data => this.render(data));
  }
}

我很不舒服地搞亂Widget的這個公共接口,並暴露實現細節。 不行。

灣 公開進口以允許嘲笑他們。

就像是:

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的風險。 我對此感到不安,但這是我迄今為止最好的想法。




@ vdloo的回答讓我朝著正確的方向前進,但在同一個文件中同時使用commonjs“exports”和ES6模塊“導出”關鍵字並不適用於我(webpack v2抱怨)。 相反,我使用一個默認(命名變量)導出來包裝所有單獨的命名模塊導出,然後在我的測試文件中導入默認導出。 我正在使用以下導出設置與摩卡/ 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);
  });
});



Links