如何模拟ES6模块的导入?

2020/11/13 14:22 · javascript ·  · 0评论

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

我正在寻找一种使用的模拟实例测试Widget的方法getDataFromServer如果我使用单独<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();
  });
});

好的,但是现在getDataFromServer不可用window(嗯,根本没有window),而且我不知道一种将东西直接注入widget.js自己的作用域的方法。

那我从这里去哪里呢?

  1. 有没有办法访问的范围widget.js,或者至少用我自己的代码替换其导入?
  2. 如果没有,我该如何进行Widget测试?

我考虑过的东西:

一种。手动依赖项注入。

从中删除所有导入,widget.js并期望调用方提供dep。

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

我对这样弄乱Widget的公共接口并暴露实现细节感到非常不自在。不行


b。暴露导入以允许对其进行嘲笑。

就像是:

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全部使用的风险我对此感到不安,但这是到目前为止最好的主意。

我已经开始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是正确的,但请注意,如果要监视模块中的一个函数并在该模块中使用另一个函数来调用该函数,则需要将该函数作为导出名称空间的一部分进行调用,否则将不会使用该间谍。

错误的例子:

// File mymodule.js

export function myfunc2() {return 2;}
export function myfunc1() {return myfunc2();}

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

// File 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
    });
});

vdloo的答案使我朝着正确的方向前进,但是在同一个文件中同时使用CommonJS “ exports”和ES6 module“ export”关键字对我不起作用(Webpack v2或更高版本抱怨)。

取而代之的是,我使用默认的(命名变量)导出包装所有单独的命名模块导出,然后将默认导出导入到我的测试文件中。我正在将以下导出设置与Mocha / Sinon一起使用,并且无需重新布线即可进行存根工作,等等:

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

我实现了一个库,该库试图解决TypeScript类导入的运行时模拟问题,而无需原始类知道任何显式依赖项注入。

该库使用import * as语法,然后用存根类替换原始导出的对象。它保留了类型安全性,因此如果在不更新相应测试的情况下更新了方法名称,则测试将在编译时中断。

可以在以下位置找到该库:ts-mock-imports

我发现此语法有效:

我的模块:

// File mymod.js
import shortid from 'shortid';

const myfunc = () => shortid();
export default myfunc;

我模块的测试代码:

// File mymod.test.js
import myfunc from './mymod';
import shortid from 'shortid';

jest.mock('shortid');

describe('mocks shortid', () => {
  it('works', () => {
    shortid.mockImplementation(() => 1);
    expect(myfunc()).toEqual(1);
  });
});

请参阅文档

我自己还没有尝试过,但我认为 嘲笑可能有用它允许您用提供的模拟代替实际模块。以下是一个示例,可让您了解其工作原理:

mockery.enable();
var networkMock = {
    getDataFromServer: function () { /* your mock code */ }
};
mockery.registerMock('network.js', networkMock);

import { Widget } from 'widget.js';
// This widget will have imported the `networkMock` instead of the real 'network.js'

mockery.deregisterMock('network.js');
mockery.disable();

似乎mockery不再需要维护了,我认为它仅能与Node.js一起使用,但是,它是一个精巧的解决方案,用于模拟那些很难模拟的模块。

我最近发现babel-plugin-mockable-imports可以很好地处理此问题,恕我直言。如果您已经在使用Babel,那么值得研究。

请参见假设我想模拟从isDevMode()函数返回的结果,以便检查在某些情况下代码的行为。

下面的示例已针对以下设置进行了测试

    "@angular/core": "~9.1.3",
    "karma": "~5.1.0",
    "karma-jasmine": "~3.3.1",

这是一个简单的测试用例场景的示例

import * as coreLobrary from '@angular/core';
import { urlBuilder } from '@app/util';

const isDevMode = jasmine.createSpy().and.returnValue(true);

Object.defineProperty(coreLibrary, 'isDevMode', {
  value: isDevMode
});

describe('url builder', () => {
  it('should build url for prod', () => {
    isDevMode.and.returnValue(false);
    expect(urlBuilder.build('/api/users').toBe('https://api.acme.enterprise.com/users');
  });

  it('should build url for dev', () => {
    isDevMode.and.returnValue(true);
    expect(urlBuilder.build('/api/users').toBe('localhost:3000/api/users');
  });
});

的示例内容 src/app/util/url-builder.ts

import { isDevMode } from '@angular/core';
import { environment } from '@root/environments';

export function urlBuilder(urlPath: string): string {
  const base = isDevMode() ? environment.API_PROD_URI ? environment.API_LOCAL_URI;

  return new URL(urlPath, base).toJSON();
}
本文地址:http://javascript.askforanswer.com/ruhemonies6mokuaidedaoru.html
文章标签: ,   ,   ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!