在节点中模拟构造函数

时间:2015-11-13 22:09:43

标签: node.js unit-testing testing tdd sinon

使用sinon的其他节点开发人员如何在单元测试中模拟构造函数调用?例如,假设我有一些函数foo

function foo() {
  var dependency = new Dependency(args);
  // do stuff with dependency
}
exports.module.foo = foo;

并且在一个单独的测试文件中我有一些测试,其中我想验证调用Dependency构造函数(args),并且我需要控制它返回的内容

it('should call Dependency constructor with bar', function() {
  var foo = require('myModule').foo
  var DependencyMock; //code to make the mock

  foo();
  expect(DependencyMock.calledWith(bar)).to.equal(true);
});

问题是sinon只能模拟附加到对象的函数,因此我们必须在构造函数被模拟之前将其附加到对象。

我一直在做的只是让一个对象将构造函数附加到模块中进行构造函数调用,将构造函数作为该对象的方法调用,然后导出对象以在测试中使用它:

var Dependency = require('path/to/dependency');

var namespace = {
  Dependency: Dependency
}

function foo() {
  var dependency = new namespace.Dependency(args);
  // do stuff with dependency
}
exports.moduole.foo = foo;
exports.module.namespace = namespace;

testfile的:

it('should call Dependency constructor with bar', function() {
  var foo = require('myModule').foo;
  var namespace = require('myModule').namespace;

  var DependencyMock = sinon.mock(namespace, 'Dependency').returns(0);
  foo();
  expect(DependencyMock.calledWith(bar)).to.equal(true);
});

这很有效,但是为了测试它而在模块上公开一个对象真的很笨拙。

任何提示?

4 个答案:

答案 0 :(得分:6)

我认为值得问为什么你想要模拟依赖的构造函数而不是注入依赖

考虑您的示例代码:

// in "foo.js"
function foo() {
  var dependency = new Dependency(args);
  // do stuff with dependency
}
exports.module.foo = foo;

如果Dependency需要foo才能工作,您可以将其作为foo的参数注入:

// in "foo.js"
function foo(dependency) {
  // do stuff with dependency
}
exports.module.foo = foo;

// in "bar.js"
var foo = require('./foo.js')(new Dependency(args));

通过此更改,现在可以轻松地在测试中注入任何Test Double(要了解有关JavaScript Test Doubles的更多信息,请查看我的article on the subject)。

这种方法使您的函数/模块的依赖性显式化,但需要您在某些时候连接它们(此处:require('./foo.js')(new Dependency(args));)。

如果您不想手动连接,可以使用rewirereplacing constructor with factory method采取另一种方法:

// in "dependency.js"
module.exports= function(args) {
  return new Dependency(args);
}

// in "foo.js"
var dependency = require('./dependency);

function foo() {
  var dep = dependency(args);
  // do stuff with dependency
}
exports.module.foo = foo;

并在你的测试中:

var rewire = require("rewire"),
    foo    = rewire("../lib/foo.js");

it('should call dependency... ', function() {
   foo.__set__("dependency", /* some spy */ );

   foo();
});

希望这有帮助!

答案 1 :(得分:2)

我使用了一种解决方法:

// Keep a reference to your dependancy class.
const dependencyClass = Dependency;
let item = new Dependency();
// Replace class with a stub.(optionally return an item.
Dependency = sinon.stub(Dependency, 'constructor').returns(item);

// Call your code that you expect the class constructor to be called.
foo();

assert.isTrue(Dependency.calledWithNew());
assert.equal(1, Dependency.callCount);
// Restore class reference.
Dependency = dependencyClass;

此外,在上述情况下,返回一个项目,因此用户可以访问依赖项以进行进一步测试。 例如

assert.equal(item.someValue, 10);

使用此方法可能会遇到其他问题,例如,已定义的属性将不再适用于该类。 我同意Jan Molek,如果你不能改变代码,请使用它。

答案 2 :(得分:0)

没试过,但是这可以工作:存根Dependency的构造函数并让它返回一个模拟。

var constructor = sinon.stub(Dependency.prototype, "constructor");
constructor.returns(sinon.mock(Dependency));

答案 3 :(得分:0)

我遇到了暴露函数而不是对象的模块的类似问题 - this was my solution。只是把这个作为解决问题的另一种方式,但我必须说,正如Jan Molak所说,通过更好的设计解决问题似乎是一种更好的方法。