2011-12-15 65 views
22

我试图让单元测试我的JavaScript更好。我有以下代码:stub出一个jQuery选择器调用?

var categoryVal = $('#category').val(); 
if (categoryVal === '') { 
    doSomething(); 
} 

我的测试运行没有在页面上输入#category,所以我会如何存根/模拟出来的jQuery选择这里?我已经查看了jasminsinon文档,但无法弄清楚如何让它们在这里工作,因为它们的存根控制对象,而$则不是。

+2

我没有使用过这些库,但`$`实际上是一个对象(函数是JavaScript中的对象)。除此之外,当没有选择任何东西时,jQuery函数就会工作 - `.val()`只会返回'undefined`。 – pimvdb 2011-12-15 17:17:40

+0

我得到`.VAL()`返回`undefined`,但我怎么能那么存根来测试我的'===“”'? – swilliams 2011-12-15 17:35:03

回答

33

这里的问题是,$()是一个函数,返回一个对象的方法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); 
} 

虽然在第一种情况下,你必须存根$返回一个存根,在第二种情况下,你可以很容易地通过存根和间谍给你的函数。

所以对于第二个兴农测试是这样的:

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的测试你的应用程序逻辑在你的应用程序。

+2

我最终做了这样的事情。我将所有的DOM操作重构为单独的函数,并将这些调用存储到这些函数中。有保持我的方法额外苗条的额外好处。 – swilliams 2011-12-15 21:13:33

+1

感谢您对此问题的详细解释。我在节点js中遇到了这个问题,但是当我试图分配给$时,我得到一个错误,说$未定义。我想这是来自严格的模式。任何想法如何解决这个问题? – 2016-10-05 21:01:42

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结构示例:有了这个设置,您可以只在您的list_test.html的“#category”分区,如果你不已经碰巧有它里面一个

/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.) 

的意见。

9

jQuery使用引擎盖下的CSS选择器引擎灒,被隔绝,因而有只有少数几个地方,它钩住你可以拦截是为了避免与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"