具有sinon.js的nodejs中全局依赖关系的新实例方法的存根

时间:2015-06-14 01:35:10

标签: javascript node.js unit-testing sinon

很抱歉这个令人困惑的标题,我不知道如何更好地描述它。我们来看看代码:

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();

测试:

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')在每个方法中被调用,我可以使它工作,我在那里存根客户端:

var client = require('some-external-lib');

sinon.stub(client, 'createClient').returns({doWork:function(){})

但是每次调用每个方法时都会初始化客户端感觉不对。

上面的单元测试代码是否有更好的方法?

:我创建了一个最小化的工作演示来演示我的意思:https://github.com/markni/Stackoverflow30825202(简单npm install && npm test,观看测试失败。)这个问题寻求解决方案测试通过而不更改主代码。

3 个答案:

答案 0 :(得分:2)

问题出现在测试定义的位置。事实是,在Node.js中,进行依赖注入相当困难。在研究它的答案时,我遇到了一个有趣的article,其中DI是通过自定义loadmodule函数实现的。这是一个相当复杂的解决方案,但也许最终你会遇到它,所以我认为值得一提。除了DI之外,它还可以访问受测模块的私有变量和函数。

要解决问题中描述的直接问题,您可以存根some-external-lib模块的客户端创建方法。

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

但是,如果您的测试变得更复杂并且模拟的客户端获得状态,您将不得不手动处理重置不同单元测试之间的状态。您的测试应该与它们的启动顺序无关。这是重置beforeEach部分中所有内容的最重要原因

答案 1 :(得分:1)

您可以使用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();
    });
})

答案 2 :(得分:0)

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

yield没有返回存根。此外,yield期望已经使用回调参数调用存根。

否则,我认为你在95%的路上。您可以简单地删除存根:

,而不是为每个测试重新初始化
describe('doSomething method', function() {
   it('should call client.doWork()',function(){
      var stub = sinon.stub(client,'doWork');
      MyClass.doSomething();
      assert(stub.calledOnce);
      stub.restore();
   })
})
BTW,另一张海报建议使用Sinon沙箱,这是一种自动删除存根的便捷方式。