如何用sinon.js将全局依赖项的新实例方法存留在nodejs中

对不起,标题混乱,我不知道如何更好地描述它。 让我们看看代码:

var client = require('some-external-lib').createClient('config string'); //constructor function MyClass(){ } MyClass.prototype.doSomething = function(a,b){ client.doWork(a+b); } MyClass.prototype.doSomethingElse = function(c,d){ client.doWork(c*d); } module.exports = new MyClass(); 

testing:

 var sinon = require('sinon'); var MyClass = requre('./myclass'); var client = require('some-external-lib').createClient('config string'); describe('doSomething method', function() { it('should call client.doWork()',function(){ var stub = sinon.stub(client,'doWork'); MyClass.doSomething(); assert(stub.calledOnce); //not working! returns false }) }) 

我可以得到它的工作,如果.createClient('xxx')在每个方法中调用,而我stub客户端与:

 var client = require('some-external-lib'); sinon.stub(client, 'createClient').returns({doWork:function(){}) 

但是,每次调用每个方法时,都要启动客户端,这种感觉是错误的。

有没有更好的方式来unit testing代码上面?


:我已经创build了一个最小的工作演示来演示我的意思: https : //github.com/markni/Stackoverflow30825202 (简单的npm install && npm test ,看testing失败。)这个问题寻求一个解决scheme,使testing通过没有改变主要代码。

问题出现在testing定义的地方。 事实是,在Node.js中,执行dependency injection相当困难。 在对你的答案进行研究时,我遇到了一个有趣的文章 ,其中DI是通过定制的loadmodule函数实现的。 这是一个相当复杂的解决scheme,但也许最终你会来,所以我觉得值得一提。 除了DI之外,还可以访问被测模块的私有variables和function。

为了解决在你的问题中描述的直接问题,你可以存储some-external-lib模块的客户端创build方法。

 var sinon = require('sinon'); //instantiate some-external-lib var client = require('some-external-lib'); //stub the function of the client to create a mocked client sinon.stub(client, 'createClient').returns({doWork:function(){}) //due to singleton nature of modules `require('some-external-lib')` inside //myClass module will get the same client that you have already stubbed var MyClass = require('./myclass');//inside this your stubbed version of createClient //will be called. //It will return a mock instead of a real client 

但是,如果你的testing变得更加复杂,模拟的客户端得到一个状态,你将不得不手动重新设置不同的unit testing之间的状态。 您的testing应该独立于它们的启动顺序。这是在beforeEach节之前重置所有内容的最重要的原因

你可以使用beforeEach()和afterEach()挂钩存根全局依赖。

 var sinon = require('sinon'); var MyClass = requre('./myclass'); var client = require('some-external-lib').createClient('config string'); describe('doSomething method', function() { beforeEach(function () { // Init global scope here sandbox = sinon.sandbox.create(); }); it('should call client.doWork()',function(){ var stub = sinon.stub(client,'doWork').yield(); MyClass.doSomething(); assert(stub.calledOnce); //not working! returns false }) afterEach(function () { // Clean up global scope here sandbox.restore(); }); }) 

部分问题在这里: var stub = sinon.stub(client,'doWork').yield();

yield不会返回存根。 另外, yield期望存根已经被调用了一个callback参数。

否则,我认为你是95%的方式。 而不是重新初始化每个testing,你可以简单地删除存根:

 describe('doSomething method', function() { it('should call client.doWork()',function(){ var stub = sinon.stub(client,'doWork'); MyClass.doSomething(); assert(stub.calledOnce); stub.restore(); }) }) 

顺便说一句,另一个海报build议使用Sinon沙箱,这是一个方便的方式来自动删除存根。