Javascript 模拟/存根仅在闭包中定义的对象
首先,为了测试我的库,我正在使用摩卡和柴,但我可能也会在某个时候需要Sinon 这是图书馆:Javascript 模拟/存根仅在闭包中定义的对象,javascript,unit-testing,ecmascript-6,mocha.js,chai,Javascript,Unit Testing,Ecmascript 6,Mocha.js,Chai,首先,为了测试我的库,我正在使用摩卡和柴,但我可能也会在某个时候需要Sinon 这是图书馆: import Service from 'service'; // a third-party module out of my control const service = Service(...); class MyLib { ... uses `service` in a bunch of different ways ... ... service.put(foo) ...
import Service from 'service'; // a third-party module out of my control
const service = Service(...);
class MyLib {
... uses `service` in a bunch of different ways ...
... service.put(foo) ...
... service.get(bar) ...
}
export default MyLib;
这基本上就是测试文件:
import MyLib from '../my-lib.js';
describe('MyLib', () => {
describe('a method that uses the service', () => {
...
服务
对象对远程服务器进行了一些调用,这在测试中是无法实现的。因此,我认为应该存根服务的方法或模拟整个服务对象。但是,由于对象是常量,并且只能通过MyLib
闭包访问,因此我不知道如何访问
理想情况下,我不希望将MyLib
的API更改为例如在构造函数中注入服务对象
如果重要的话,我会将Babel 6与es2015预设一起使用
我应该如何处理这个问题?您对
MyLib
的测试不应该测试服务
,因此我建议对其全部或大部分进行模拟。您应该检查MyLib
是否使用正确的参数在service
上调用了正确的函数
我对sinon不太清楚,但这看起来是导入和使用库的一种非常标准的方式,如果它不支持mocking
服务,我会感到惊讶。有几种方法可以做到这一点
没有额外库的最简单方法
将服务另存为类属性并从中调用:
import Service from 'service';
const service = Service(...);
class MyLib {
constructor() {
this.service = service;
}
... now you should call service in a bit different way
... this.service.put(foo) ...
... this.service.get(bar) ...
}
export default MyLib;
然后,您可以在测试中重写服务实例:
it('should call my mock', () => {
const lib = new MyLib();
lib.service = mockedService; // you need to setup this mock, with Sinon, for example
lib.doSomething();
assert.ok(mockedService.put.calledOnce); // works
});
Mock require()函数
有些库允许您重写require()
函数的结果。我最喜欢的是。您可以使用它,您的模块将获得mockedSerice而不是real:
import proxyquire from 'proxyquire';
it('should call my mock', () => {
const MyLib = proxyquire('./my-lib', {
// pass here the map of mocked imports
service: mockedService
})
const lib = new MyLib();
lib.doSomething();
assert.ok(mockedService.put.calledOnce); // works
});
使用重新布线
进入模块闭合区
是一个特殊的库,用于插入模块代码,以便您可以在其中更改任何局部变量
import rewire from 'rewire';
it('should call my mock', () => {
const MyLib = rewire('./my-lib')
const lib = new MyLib();
// __set__ is a special secret method added by rewire
MyLib.__set__('service', mockedService);
lib.doSomething();
assert.ok(mockedService.put.calledOnce); // works
});
此外,还有一个更好地与您的工具集成的方法
以上所有方法都很好,您可以选择更适合您的问题。我最近也在处理同样的问题。
你可以利用
在您正在编写的测试中,要求/导入服务的方式与您在测试文件中所做的方式相同。
然后使用Sinon在要测试的文件中存根您正在使用的方法
sinon.stub(Service, put).returns()
当文件需要服务时,它将使用修改后的模块
我还没有测试过您的具体案例,您正在创建服务实例,并且只有在使用它之后,才可以使用它,但是稍微玩玩一下它应该可以帮助您实现您想要的,而且它没有任何外部库,只有简单的sinon存根。我同意您建议的方法,但问题是,服务
只能通过MyLib
访问,因为它们共享相同的闭包。因此,我不知道如何存根/模拟它。我假设您使用的是Babel,因为这是ES2015语法。看起来sinon不支持这样的模拟依赖项,您可能需要填充模拟。我在babel号角追踪器上读到了关于sinon的信息,这让我想到了。是的,我在es2015预设中使用了babel。我将把这一点添加到帖子中。基本上,babel-plugin-rewire
应该让你用其他东西来代替service
,而不是用mock来修补service
,在本例中,是一个mock对象。这是一个微妙的区别,但由于您导入的是服务
而不是注入依赖项,因此您需要替换从“服务”导入的import*带来的内容
,您可以尝试通过这一点来模拟您的服务
,这是一个全面而令人敬畏的过程。我很欣赏您分享的所有技术,因为我可以从第一种方法开始测试以简化测试,然后迁移到其他方法以获得更好的编码风格