我正在努力改进我的JavaScript单元测试。我有以下代码:
var categoryVal = $('#category').val();
if (categoryVal === '') {
doSomething();
}
我的测试运行器在页面上没有#category
输入,那么我如何在这里存根/模拟jQuery选择器呢?我查看了jasmin和sinon文档,但无法弄清楚如何让它们在这里工作,因为它们的存根操作对象,$
不是。
答案 0 :(得分:35)
这里的问题是$()
是一个返回方法val()
的对象的函数。因此,您必须使用存根$()来返回具有方法val。
$ = sinon.stub();
$.withArgs('#category').returns(sinon.stub({val: function(){}}));
但这里的主要错误是让你想要测试的代码调用函数$()来创建新的实例。为什么?最佳实践是在类中不创建新实例,而是将它们传递给构造函数。假设您有一个函数可以从输入中获取值,将其加倍,然后将其写回另一个:
function doubleIt(){
$('#el2').val(('#el1').val() *2);
}
在这种情况下,您可以通过调用$()
创建2个新对象。现在你必须存根$()
来返回一个模拟和一个存根。使用下一个示例,您可以避免这种情况:
function doubleIt(el1, el2){
el2.val(el1.val() *2);
}
虽然在第一种情况下你必须以stub来返回存根,但在第二种情况下,你可以轻松地将存根和间谍传递给你的函数。
所以第二个的sinon测试看起来像这样:
var el1 = sinon.stub({val: function(){}});
el1.returns(2);
var el2 = sinon.spy({val: function(){}}, 'val')
doubleIt(el1, el2)
assert(el2.withArgs(4).calledOnce)
因此,由于此处没有dom元素,因此您只需测试应用程序逻辑,而无需在应用程序中创建相同的dom。
答案 1 :(得分:10)
jQuery在引擎盖下使用了css选择器引擎Sizzle并且被解耦,因此它只有几个地方可以挂钩。你可以截取它以避免与dom的任何交互。
jQuery.find 是重要的一个,可以根据需要进行更改。 Sinon可以在这里使用或临时交换功能。
例如
existingEngine = jQuery.find
jQuery.find = function(selector){ console.log(selector) }
$(".test")
//>> ".test"
jQuery.find = existingEngine
您还可以应用具有后备的特定捕获条件
existingEngine = jQuery.find
jQuery.find = function(selector){
if(selector=='blah'}{ return "test"; }
return existingEngine.find.apply(existingEngine, arguments)
}
在我最近的工作中,我创建了一个虚拟对象,它像dom节点一样响应并将其包装在jQuery对象中。然后,这将正确响应val()并使所有jquery方法都出现。在我的情况下,我只是从表单中提取值。如果你正在进行实际的操作,你可能需要比这更聪明,或许用jQuery创建一个代表你期望的临时dom节点。
obj = {
value: "blah",
type: "text",
nodeName: "input",
}
$(obj).val(); // "blah"
答案 2 :(得分:1)
如果您使用的是Backbone.js和Jasmin,这是一个非常好的测试视图的指南。向下滚动到“查看”部分。
http://tinnedfruit.com/2011/04/26/testing-backbone-apps-with-jasmine-sinon-3.html
没错,存根操作对象。我想创建一个视图存根就像这样。
this.todoViewStub = sinon.stub(window, "TodoView")
.returns(this.todoView);
只是为了以后能够渲染视图。
this.view.render();
换句话说,将'#category'div添加到testrunner的DOM中,以便$可以对其进行操作。如果您的“#category”div不在this.view中,那么您可以创建一个test.html页面,在该页面中运行您的隔离测试。这是Javascript MVC框架中的一种常见模式,我更习惯于Backbone。
这是一个简单的JMVC应用程序结构示例:
/todo
/models
todo.js
/list
/views
init.tmpl
listItem.tmpl
list.css
list.js (Controller)
unitTest.js (Tests for your list.)
list_test.html (A html for your unit tests to run on.)
有了这个设置,你可以在list_test.html中包含“#category”div,如果你还没有把它放在其中一个视图中。