在Sinon中对整个班级进行测试

时间:2016-03-02 02:03:00

标签: javascript node.js unit-testing sinon

序言:我已经阅读了很多SO和博客文章,但没有看到任何可以回答这个特定问题的内容。也许我只是在寻找错误的东西......

假设我开发了一个WidgetManager类,它将对Widget个对象进行操作。

如何使用sinon测试WidgetManager是否正确使用Widget API而不拉入整个Widget库?

基本原理:WidgetManager的测试应该与Widget类分离。也许我还没有编写Widget,或者Widget可能是一个外部库。无论哪种方式,我都应该能够测试WidgetManager是否正确使用Widget的API而无需创建真实的Widgets。

我知道sinon模拟只能在现有的类上工作,据我所知,sinon存根也需要该类存在才能被存根。

为了使其具体化,我如何测试Widget.create()只用一个参数' name'在以下代码中?

正在测试的代码

// file: widget-manager.js

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

WidgetManager.prototype.addWidget = function(name) {
    this.widgets.push(Widget.create(name));
}

测试代码

// file: widget-manager-test.js

var WidgetManager = require('../lib/widget-manager.js')
var sinon = require('sinon');

describe('WidgetManager', function() {
  describe('#addWidget', function() {
    it('should call Widget.create with the correct name', function() {
      var widget_manager = new WidgetManager();
      // what goes here?
    });

    it('should push one widget onto the widgets list', function() {
      var widget_manager = new WidgetManager();
      // what setup goes here?
      widget_manager.addWidget('fred');
      expect(widget_manager.widgets.length).to.equal(1);
  });
});

旁白:当然,我可以用适当的方法定义一个MockWidget类进行测试,但我对真正学习如何使用sinon的间谍/存根更感兴趣/ mock设施正确。

2 个答案:

答案 0 :(得分:9)

答案实际上是依赖注入。

您希望以预期的方式测试WidgetManager是否与依赖Widget)进行交互 - 并且您希望自由地操纵和询问该依赖关系。为此,您需要在测试时注入Widget的存根版本。

根据WidgetManager的创建方式,有几种依赖注入选项。

一个简单的方法是允许将Widget依赖项注入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));
}

然后在测试中,您只需将存根Widget传递给正在测试的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的交互。

还有像proxyquirerewire这样的选项,可以在测试时为覆盖依赖项提供更强大的选项。最合适的选择是实现和首选 - 但在所有情况下,您只是想在测试时替换给定的依赖项。

答案 1 :(得分:3)

您的addWidget方法执行了两项操作:

  • 将字符串“转换”为Widget个实例;
  • 将该实例添加到内部存储。

我建议您更改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);