Mocking/stubbing 只在闭包中定义的对象
Mocking/stubbing objects that are only defined in a closure
首先,为了测试我的库,我使用的是 Mocha 和 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) ...
... service.get(bar) ...
}
export default MyLib;
这基本上是测试文件:
import MyLib from '../my-lib.js';
describe('MyLib', () => {
describe('a method that uses the service', () => {
...
service
对象对远程服务器进行了一些调用,我在测试中确实无法做到这一点。因此,我认为我应该存根服务的方法或模拟整个服务对象。但是,由于该对象是常量并且只能通过 MyLib
闭包访问,所以我不知道如何实现。
理想情况下,我不希望将 MyLib
的 API 更改为例如在构造函数中注入服务对象。
我使用带有 es2015
预设的 Babel 6,如果重要的话。
我该如何处理?
您对 MyLib
的测试不应测试 service
,因此我建议模拟全部或大部分。您应该检查 MyLib
是否使用正确的参数在 service
上调用正确的函数。
我不确定 sinon,但这看起来是一种非常标准的导入和使用库的方法,如果它不支持模拟,我会感到惊讶 service
。
有几种方法可以做到。
没有额外库的最简单方法
将服务另存为 class 属性 并从那里调用它:
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
});
模拟 require() 函数
有些库允许您覆盖 require()
函数的结果。我最喜欢的是 proxyquire。您可以使用它,您的模块将获得 mockedSerice 而不是真实的:
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
});
使用rewire
获取模块闭包的访问权限
Rewire 是一个检测模块代码的特殊库,因此您可以在那里更改任何局部变量
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
});
此外,还有一个 babel-plugin-rewire 可以更好地与您的工具集成。
以上所有方法都不错,您可以选择更适合您的问题。
我最近也在处理同样的事情。
您可以利用 https://nodejs.org/api/modules.html#modules_caching
在您正在编写的测试中,require/import 服务的方式与您在正在测试的文件中的方式相同。
然后使用 Sinon 将您正在使用的方法存根到文件中进行测试。
sinon.stub(Service, put).returns()
当文件需要服务时,它将使用修改后的模块。
我还没有测试过你的确切情况,你正在创建服务实例,然后才使用它,但稍微玩一下它应该可以帮助你实现你想要的,仅此而已没有任何外部库,只有简单的 sinon 存根。
首先,为了测试我的库,我使用的是 Mocha 和 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) ...
... service.get(bar) ...
}
export default MyLib;
这基本上是测试文件:
import MyLib from '../my-lib.js';
describe('MyLib', () => {
describe('a method that uses the service', () => {
...
service
对象对远程服务器进行了一些调用,我在测试中确实无法做到这一点。因此,我认为我应该存根服务的方法或模拟整个服务对象。但是,由于该对象是常量并且只能通过 MyLib
闭包访问,所以我不知道如何实现。
理想情况下,我不希望将 MyLib
的 API 更改为例如在构造函数中注入服务对象。
我使用带有 es2015
预设的 Babel 6,如果重要的话。
我该如何处理?
您对 MyLib
的测试不应测试 service
,因此我建议模拟全部或大部分。您应该检查 MyLib
是否使用正确的参数在 service
上调用正确的函数。
我不确定 sinon,但这看起来是一种非常标准的导入和使用库的方法,如果它不支持模拟,我会感到惊讶 service
。
有几种方法可以做到。
没有额外库的最简单方法
将服务另存为 class 属性 并从那里调用它:
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
});
模拟 require() 函数
有些库允许您覆盖 require()
函数的结果。我最喜欢的是 proxyquire。您可以使用它,您的模块将获得 mockedSerice 而不是真实的:
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
});
使用rewire
获取模块闭包的访问权限
Rewire 是一个检测模块代码的特殊库,因此您可以在那里更改任何局部变量
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
});
此外,还有一个 babel-plugin-rewire 可以更好地与您的工具集成。
以上所有方法都不错,您可以选择更适合您的问题。
我最近也在处理同样的事情。 您可以利用 https://nodejs.org/api/modules.html#modules_caching 在您正在编写的测试中,require/import 服务的方式与您在正在测试的文件中的方式相同。 然后使用 Sinon 将您正在使用的方法存根到文件中进行测试。
sinon.stub(Service, put).returns()
当文件需要服务时,它将使用修改后的模块。
我还没有测试过你的确切情况,你正在创建服务实例,然后才使用它,但稍微玩一下它应该可以帮助你实现你想要的,仅此而已没有任何外部库,只有简单的 sinon 存根。