23

我正在尝试更好地对我的 JavaScript 进行单元测试。我有以下代码:

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

我的测试运行器在页面上没有#category输入,那么我将如何在此处存根/模拟 jQuery 选择器?我查看了jasminsinon文档,但无法弄清楚如何让它们在这里工作,因为它们的存根对对象进行操作,但事实$并非如此。

4

3 回答 3

37

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

在第一种情况下,您必须存根 $ 以返回存根,在第二种情况下,您可以轻松地将存根和间谍传递到您的函数中。

所以第二个的 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。

于 2011-12-15T20:57:55.460 回答
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"
于 2012-03-29T23:34:54.530 回答
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.)

有了这个设置,如果您还没有碰巧在其中一个视图中包含“#category” div,则可以在 list_test.html 中包含它。

于 2011-12-15T21:01:39.507 回答