把整个class级用sintesting

序言:我读过很多关于SO和博客的文章,但还没有看到任何回答这个问题的东西。 也许我只是在寻找错误的东西…

假设我正在开发一个将在Widget对象上运行的WidgetManager类。

如何使用sinon来testingWidgetManager是否正确使用Widget API,而无需拉动整个Widget库?

基本原理:WidgetManager的testing应该从Widget类中分离出来。 也许我还没有写Widget,或者Widget是一个外部库。 无论哪种方式,我应该能够testingWidgetManager是正确使用Widget的API,而无需创build真正的Widgets。

我知道sinon mocks只能在现有的类上工作,而且据我所知,sinon stubs也需要这个类存在,才能被截断。

为了使其具体,我将如何testingWidget.create()正在调用一个参数名称在下面的代码一次?

代码正在testing中

 // file: widget-manager.js function WidgetManager() { this.widgets = [] } WidgetManager.prototype.addWidget = function(name) { this.widgets.push(Widget.create(name)); } 

testing代码

 // 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类来进行testing,但是我更关心如何正确地使用sinon的间谍/存根/模拟工具。

答案是关于dependency injection。

你想testingWidgetManager是否以预期的方式与一个依赖Widget )进行交互 – 并且你想要自由地操作和查询这个依赖。 要做到这一点,你需要在testing时注入一个微件版本的Widget

根据WidgetManager的创build方式,dependency injection有几个选项。

一个简单的方法是允许将Widgetdependency injection到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)); } 

然后在testing中,您只需将一个存根Widget传递给被testing的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'); }); 

您可以根据特定testing的需要修改存根的行为。 例如,要testing窗口小部件创build后窗口部件列表的长度是否增加,您可以简单地从您的stubbed create()方法中返回一个对象:

  var stubbedWidget = { create: sinon.stub().returns({}) } 

这使您可以完全控制依赖关系,而无需模拟或存储所有方法,并可以testing与API的交互。

还有像proxyquire或rewire这样的选项,可以在testing时提供更强大的覆盖依赖关系选项。 最合适的select是实现和偏好 – 但在所有情况下,您只是试图在testing时间replace给定的依赖项。

你的addWidget方法有两件事情:

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

我build议你改变addWidget签名直接接受实例,而不是名称,并移出创build一些其他的地方。 将使testing更容易:

 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]); 

之后,您将需要一种按名称创build小部件的方法,该方法应该非常简单,以便testing:

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