在节点中模拟构造函数

The*_*rry 6 testing tdd unit-testing node.js sinon

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

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

在一个单独的测试文件中我有一些测试,其中我想验证调用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);
});
Run Code Online (Sandbox Code Playgroud)

问题是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;
Run Code Online (Sandbox Code Playgroud)

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);
});
Run Code Online (Sandbox Code Playgroud)

这是有效的,但为了测试它,在我的模块上暴露一个对象感觉非常笨拙.

有小费吗?

Jan*_*lak 6

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

考虑您的示例代码:

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

如果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));
Run Code Online (Sandbox Code Playgroud)

有了这个改变,现在在你的测试中注入任何Test Double都是微不足道的(要了解更多有关JavaScript Test Doubles的信息,请查看我关于这个主题的文章).

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


如果您不想手动连接,可以使用重新连接使用工厂方法替换构造函数来采用另一种方法:

// 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;
Run Code Online (Sandbox Code Playgroud)

在你的测试中:

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

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

   foo();
});
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助!

一月


Jan*_*tis 5

我为此使用了一种解决方法:

// 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;
Run Code Online (Sandbox Code Playgroud)

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

assert.equal(item.someValue, 10);
Run Code Online (Sandbox Code Playgroud)

使用它可能会遇到其他问题,例如定义的属性将不再可用于该类。我同意 Jan Molek 的观点,如果您无法更改代码,请使用它。