Javascript 如何模拟 ES6 模块的导入?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/35240469/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to mock the imports of an ES6 module?
提问by Kos
I have the following ES6 modules:
我有以下 ES6 模块:
network.js
网络.js
export function getDataFromServer() {
return ...
}
widget.js
小部件.js
import { getDataFromServer } from 'network.js';
export class Widget() {
constructor() {
getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
render() {
...
}
}
I'm looking for a way to test Widget with a mock instance of getDataFromServer
. If I used separate <script>
s instead of ES6 modules, like in Karma, I could write my test like:
我正在寻找一种方法来测试 Widget 的模拟实例getDataFromServer
。如果我使用单独的<script>
s 而不是 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();
});
});
However, if I'm testing ES6 modules individually outside of a browser (like with Mocha + babel), I would write something like:
但是,如果我在浏览器之外单独测试 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();
});
});
Okay, but now getDataFromServer
is not available in window
(well, there's no window
at all), and I don't know a way to inject stuff directly into widget.js
's own scope.
好的,但现在getDataFromServer
不可用window
(好吧,根本没有window
),而且我不知道如何将内容直接注入到widget.js
自己的作用域中。
So where do I go from here?
那么我该往哪里去呢?
- Is there a way to access the scope of
widget.js
, or at least replace its imports with my own code? - If not, how can I make
Widget
testable?
- 有没有办法访问 的范围
widget.js
,或者至少用我自己的代码替换它的导入? - 如果没有,我怎样才能进行
Widget
测试?
Stuff I considered:
我考虑过的东西:
a. Manual dependency injection.
一种。手动依赖注入。
Remove all imports from widget.js
and expect the caller to provide the deps.
删除所有导入widget.js
并期望调用者提供 deps。
export class Widget() {
constructor(deps) {
deps.getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
}
I'm very uncomfortable with messing up Widget's public interface like this and exposing implementation details. No go.
我对像这样弄乱 Widget 的公共接口并暴露实现细节感到非常不舒服。不行。
b. Expose the imports to allow mocking them.
湾 公开导入以允许模拟它们。
Something like:
就像是:
import { getDataFromServer } from 'network.js';
export let deps = {
getDataFromServer
};
export class Widget() {
constructor() {
deps.getDataFromServer("dataForWidget")
.then(data => this.render(data));
}
}
then:
然后:
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();
});
});
This is less invasive but requires me to write a lot of boilerplate for each module, and there's still a risk of me using getDataFromServer
instead of deps.getDataFromServer
all the time. I'm uneasy about it, but that's my best idea so far.
这侵入性较小,但需要我为每个模块编写大量样板,而且我仍然存在使用getDataFromServer
而不是deps.getDataFromServer
一直使用的风险。我对此感到不安,但这是我目前最好的主意。
回答by carpeliam
I've started employing the import * as obj
style within my tests, which imports all exports from a module as properties of an object which can then be mocked. I find this to be a lot cleaner than using something like rewire or proxyquire or any similar technique. I've done this most often when needing to mock Redux actions, for example. Here's what I might use for your example above:
我已经开始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();
});
});
If your function happens to be a default export, then import * as network from './network'
would produce {default: getDataFromServer}
and you can mock network.default.
如果您的函数恰好是默认导出,import * as network from './network'
则将产生{default: getDataFromServer}
并且您可以模拟 network.default。
回答by vdloo
@carpeliam is correct but note that if you want to spy on a function in a module and use another function in that module calling that function, you need to call that function as part of the exports namespace otherwise the spy won't be used.
@carpeliam 是正确的,但请注意,如果您想监视模块中的函数并使用该模块中的另一个函数调用该函数,则需要将该函数作为exports 命名空间的一部分调用,否则将不会使用间谍。
Wrong example:
错误示例:
// 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
});
});
Right example:
正确的例子:
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
});
});
回答by QuarkleMotion
@vdloo's answer got me headed in the right direction, but using both commonjs "exports" and ES6 module "export" keywords together in the same file did not work for me (webpack v2 or later complains). Instead, I'm using a default (named variable) export wrapping all of the individual named module exports and then importing the default export in my tests file. I'm using the following export setup with mocha/sinon and stubbing works fine without needing rewire, etc.:
@vdloo 的回答让我朝着正确的方向前进,但是在同一个文件中同时使用 commonjs“exports”和 ES6 模块“export”关键字对我不起作用(webpack v2 或更高版本抱怨)。相反,我使用默认(命名变量)导出来包装所有单独的命名模块导出,然后在我的测试文件中导入默认导出。我正在使用以下导出设置与 mocha/sinon 和 stubbing 工作正常,无需重新接线等:
// 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);
});
});
回答by EmandM
I implemented a library that attempts to solve the issue of run-time mocking of Typescript class imports without needing the original class to know about any explicit dependency injection.
我实现了一个库,它试图解决 Typescript 类导入的运行时模拟问题,而无需原始类知道任何显式依赖注入。
The library uses the import * as
syntax and then replaces the original exported object with a stub class. It retains type safety so your tests will break at compile time if a method name has been updated without updating the corresponding test.
该库使用import * as
语法,然后用存根类替换原始导出的对象。它保留了类型安全性,因此如果在没有更新相应测试的情况下更新了方法名称,您的测试将在编译时中断。
This library can be found here: ts-mock-imports.
这个库可以在这里找到:ts-mock-imports。
回答by nerfologist
I have found this syntax to be working:
我发现这种语法有效:
My module:
我的模块:
// mymod.js
import shortid from 'shortid';
const myfunc = () => shortid();
export default myfunc;
My module's test code:
我的模块的测试代码:
// 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);
});
});
See the doc.
请参阅文档。
回答by Erik B
I haven't tried it myself, but I think mockerymight work. It allows you to substitute the real module with a mock that you have provided. Below is an example to give you an idea of how it works:
我自己没有尝试过,但我认为嘲弄可能会奏效。它允许您用您提供的模拟替换真实模块。下面是一个例子,让你了解它是如何工作的:
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();
It seems like mockery
isn't maintained anymore and I think it only works with Node.js, but none the less, it's a neat solution for mocking modules that are otherwise hard to mock.
似乎mockery
不再维护,我认为它只适用于 Node.js,但无论如何,它是模拟模块的巧妙解决方案,否则很难模拟。