Javascript 在sinon中测试整个类

Javascript 在sinon中测试整个类,javascript,node.js,unit-testing,sinon,Javascript,Node.js,Unit Testing,Sinon,序言:我读了很多SO和博客文章,但没有看到任何能回答这个问题的东西。也许我只是找错了东西 假设我正在开发一个WidgetManager类,该类将对小部件对象进行操作 我如何使用sinon测试WidgetManager是否正确使用小部件API,而无需拉入整个小部件库 理由:WidgetManager的测试应该与小部件类解耦。也许我还没有写Widget,或者Widget是一个外部库。无论采用哪种方式,我都应该能够测试WidgetManager是否正确使用了小部件的API,而无需创建真正的小部件 我知

序言:我读了很多SO和博客文章,但没有看到任何能回答这个问题的东西。也许我只是找错了东西

假设我正在开发一个
WidgetManager
类,该类将对
小部件
对象进行操作

我如何使用sinon测试WidgetManager是否正确使用
小部件
API,而无需拉入整个
小部件

理由:WidgetManager的测试应该与小部件类解耦。也许我还没有写Widget,或者Widget是一个外部库。无论采用哪种方式,我都应该能够测试WidgetManager是否正确使用了小部件的API,而无需创建真正的小部件

我知道sinon Mock只能在现有类上工作,而且据我所知,sinon存根在存根之前也需要类存在

为了使其具体化,我将如何测试
Widget.create()
在以下代码中使用单个参数“name”只调用一次

测试中的代码 测试代码
旁白:当然,我可以定义一个
MockWidget
类,用适当的方法进行测试,但我更感兴趣的是真正学习如何正确使用sinon的spy/stub/mock工具。

答案其实是关于依赖注入的

您希望测试
WidgetManager
是否以预期的方式与依赖项(
Widget
)交互,并且希望能够自由地操作和查询该依赖项。为此,您需要在测试时插入
小部件的存根版本

依赖项注入有几个选项,具体取决于创建WidgetManager的方式

一个简单的方法是允许将
小部件
依赖项注入
WidgetManager
构造函数:

// file: widget-manager.js

function WidgetManager(Widget) {
   this.Widget = Widget;
   this.widgets = [];
}

WidgetManager.prototype.addWidget = function(name) {
    this.widgets.push(this.Widget.create(name));
}
然后在测试中,您只需将存根
小部件
传递给测试下的
WidgetManager

it('should call Widget.create with the correct name', function() {
  var stubbedWidget = {
      create: sinon.stub()
  }
  var widget_manager = new WidgetManager(stubbedWidget);
  widget_manager.addWidget('fred');
  expect(stubbedWidget.create.calledOnce);
  expect(stubbedWidget.create.args[0] === 'fred');
});
您可以根据特定测试的需要修改存根的行为。例如,要测试小部件创建后小部件列表长度是否增加,只需从存根
create()
方法返回一个对象:

  var stubbedWidget = {
      create: sinon.stub().returns({})
  }
这允许您完全控制依赖项,而不必模拟或存根所有方法,并允许您测试与其API的交互


还有像或这样的选项,它们为在测试时重写依赖项提供了更强大的选项。最合适的选择取决于实现和首选项,但在所有情况下,您的目标都是在测试时替换给定的依赖项。

您的
addWidget
方法有两个功能:

  • 将字符串“转换”为
    小部件
    实例
  • 将该实例添加到内部存储
我建议您将
addWidget
签名更改为直接接受实例,而不是名称,然后将创建移到其他地方。将使测试更容易:

Manager.prototype.addWidget = function (widget) {
  this.widgets.push(widget);
}

// no stubs needed for testing:
const manager = new Manager();
const widget = {};

manager.addWidget(widget);
assert.deepStrictEquals(manager.widgets, [widget]);


之后,您将需要一种按名称创建小部件的方法,这也应该是非常直接的测试方法:

// Maybe this belongs to other place, not necessarily Manager class…
Manager.createWidget = function (name) {
  return new Widget(name);
}

assert(Manager.createWidget('calendar') instanceof Widget);


proxyquire与sinon的结合创造了测试奇迹。这种情况下,
addWidget(widget:WidgetInstance)
addWidget(name:String)
更受欢迎。有什么东西阻止你这么做吗?这似乎是这些观点的复制品,这些都是有益的见解,值得一投。但是
returnnewwidget(name)
假定我有权访问Widget类或者我以某种方式将其存根,如果您阅读OP,这是一个错误的假定。在测试添加时,您不需要访问
Widget
。在测试
Widget
s的创建时,我并不认为使用存根有什么意义。如果您想使
管理器
独立于
小部件
s,请将创建移动到其他地方。同意!我几乎提供了另一个示例,使用
fs
作为依赖包,并希望测试(例如)
fs.readFileSync()
被我的代码调用。在那种情况下你会怎么做?(如果有帮助,我可以扩展我的示例…)
Manager.prototype.addWidget = function (widget) {
  this.widgets.push(widget);
}

// no stubs needed for testing:
const manager = new Manager();
const widget = {};

manager.addWidget(widget);
assert.deepStrictEquals(manager.widgets, [widget]);
// Maybe this belongs to other place, not necessarily Manager class…
Manager.createWidget = function (name) {
  return new Widget(name);
}

assert(Manager.createWidget('calendar') instanceof Widget);