如何使用 sinon 模拟导出为函数的节点模块的 function/property?

How can I mock a function/property of a node module exported as a function using sinon?

我有一个作为函数导出的服务模块。我需要将一些东西传递给它,比如一个配置对象,所以它确实需要保留这个结构。我试图从服务中删除一个函数,但无法弄清楚。在我的应用程序中,我有一个函数可以进行 API 调用,该调用在测试期间出现问题,因此我想对其进行存根。 (我知道我必须以不同的方式编写我的测试来处理异步问题)

// myService.js
module.exports = function(config) {
  function foo() {
    returns 'bar';
  }

  return {
    foo: foo
  };
};

// test.js
var config = require('../../config');
var request = require('supertest');
var chai = require('chai');
var expect = chai.expect;
var sinon = require('sinon');
var myService = require('./myService.js')(config);

describe('Simple test', function(done) {
  it('should expect "something else", function(done) {
    var stub = sinon.stub(myService, 'foo').returns('something else');

    request(server) // this object is passed into my test. I'm using Express
      .get('/testRoute')
      .expect(200)
      .expect(function(res) {
        expect(res.body).to.equal('something else');
        stub.restore();
      })
      .end(done);
  });
});

* /testRoute I set up as a simple GET route that simply returns the value from myService.foo()

以上方法无效,我认为这与我的服务导出方式有关。如果我如下编写服务,存根工作正常。

module.exports = {
  test: function() {
    return 'something';
  }
};

但同样,我需要能够将信息传递给模块,所以我想将我的模块保留在上面的原始结构中。有没有办法从以这种方式导出的模块中存根函数?我也在研究 proxyquire 但不确定这是否是答案。

您的测试存根不起作用的原因是每次调用模块初始值设定项时都会创建 foo 函数。正如您所发现的,当您在模块上有一个静态方法时,您就可以存根了。

这个问题有多种解决方案 -- 但最简单的是静态公开该方法。

// myService.js
module.exports = function(config) {
  return {
    foo: foo
  };
};

var foo = module.exports.foo = function foo() {
  return 'bar'
}

虽然丑陋,但有效。

如果 foo 函数对服务中的变量有一个闭包(这就是它位于服务初始值设定项中的原因)怎么办?然后不幸的是这些需要显式传入。

// myService.js
module.exports = function(config) {
  return {
    foo: foo
  };
};

var foo = module.exports.foo = function(config) {
  return function foo() {
    return config.bar;
  }
}

现在您可以安全地存根模块了。

但是,你存根的方式应该被认为是不安全的。只有当您的测试完美运行时,存根才会被清理。您应该始终在 beforeafter(或 beforeEachafterEach)固定装置内存根,例如:

// We are not configuring the module, so the call with config is not needed
var myService = require('./myService.js');

describe('Simple test', function(done) {
  beforeEach(function () {
    // First example, above 
    this.myStub = sinon.stub(myService, foo).returns('something else');
    // Second example, above
    this.myStub = sinon.stub(myService, foo).returns(function () {
      returns 'something else';
    });
  });

  afterEach(function () {
    this.myStub.restore();
  });

  it('should expect "something else", function(done) {
    request(server) // this object is passed into my test. I'm using Express
      .get('/testRoute')
      .expect(200)
      .expect(function(res) {
        expect(res.body).to.equal('something else');
       })
      .end(done);
   });
 });

还有其他选项可以使用依赖注入存根依赖。我建议你看看 https://github.com/vkarpov15/wagner-core or my own https://github.com/CaptEmulation/service-builder