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