【问题标题】:Mocking jQuery to test basic useMocking jQuery 测试基本使用
【发布时间】:2012-02-28 19:23:49
【问题描述】:

我很难理解如何设置一个允许我测试我的 jQuery 调用的对象。我不需要模拟任何异步调用或任何东西,只需基本使用。因此,让我列出我想要测试的函数(为简单起见截断):

listGamesCallback : function(data) {
     var gameList = $("#gameList select");

     gameList.empty();

     $.each(data, function() {
          var newOption = $('<option>', {value : this.gameId });
          newOption.text(string);
          newOption.data("isJoinable", isJoinable);

          // Add it to the list
          gameList.append(newOption);
     });


}

我需要在这里模拟 jQuery 来对这个方法进行单元测试,但我无法弄清楚如何在 javascript 中执行此操作。即使没有 jsMockito,我也不知道如何创建一个具有 jQuery 在这种情况下具有的属性的对象。对此的任何帮助将不胜感激。

我正在使用 jsTestDriver、jsHamcrest、jsMockito 和 jQuery。但是,创建具有这些属性的 $ 对象的通用方法也很棒。谢谢!

对于那些询问的人,这是我想出的似乎有点工作的东西..但我不明白为什么。

var saved$ = $;

var mockContruct = mockFunction();
var mockedGamelist = mock(jQuery);
var mockedOption = mock(jQuery);

mocked$ = (function() {
    var test = function(name) {
        var args = jQuery.makeArray(arguments);
        return mockContruct.call(test, args);
    };

    $.extend(test, $);

    // This is what confuses me.  This worked, but it's wierd
    // It allows me to use the regular jQuery functions like
    // $.each, while returning mocked objects when selectors are used.
    test.prototype.constructor = test;

    return test;
})();

$ = mocked$;    

when(mockContruct).call(anything(), hasItem(containsString("#gameList")))
    .thenReturn(mockedGamelist);

when(mockContruct).call(anything(), hasItems(containsString("<option>"), both(object()).and(hasMember("value"))))
        .thenReturn(mockedOption);

headerFunctions.listGamesCallback([ {
    gameId : 1,
    isWhitesTurn : false,
    isGameOver : false,
    whiteUserName : "foobar",
    blackUserName : "barfoo"
} ]);

JsMockito.verify(mockedGamelist).empty();
JsMockito.verify(mockedGamelist).append(mockedOption);

$ = saved$;

【问题讨论】:

  • 如果您也提供了测试代码,那将会很有用。 :)
  • 好的,抱歉回复晚了,但我已被重新分配,因此优先级较低。我有一种复杂的方式来运行我在问题中编辑的测试。它写得不是很好,因为我不太明白 jQuery 是如何做到的。
  • 您编写的测试并不能真正证明除了您的代码执行之外的任何东西。如果失败,它也会污染其他有问题的测试,因为如果失败,您将无法恢复 $。我认为您仍然错过了这些测试的重点:您应该为您期望从代码中获得的 行为 编写测试,而不是它如何执行的细节。您应该尝试编写一个测试,该测试只专注于在您的函数运行后证明该 gameList 具有 X 选项,其中包含任何值。

标签: javascript jquery unit-testing


【解决方案1】:

好的,这就是我想出的以最少的设置完成工作的方法。 .extend 在这里是完全必要的,以便正确设置 jQuery 对象。这允许您模拟构造函数以返回可用于运行测试的模拟 jQuery 对象。作为间谍,jQuery 将在所有情况下都按预期工作,除非您希望它做其他事情。这里是:

TestCase("HeaderTest", {
    testListGamesCallback : function () {
       var saved$ = $;

       $ = $.prototype.construct = jQuery.extend(spy(jQuery), jQuery);

       var mockGameList = mock(jQuery);
       when($)(containsString("#gameList")).thenReturn(mockGameList);

       headerFunctions.listGamesCallback([ {
          gameId : 1,
          isWhitesTurn : false,
          isGameOver : false,
          whiteUserName : "foobar",
          blackUserName : "barfoo"
       } ]);

       verify(mockGameList).empty();
       verify(mockGameList).append(object());

       $ = saved$;
   }
});

这个解决方案的警告是,除了构造函数之外,模拟任何东西都有点棘手。您必须设置要模拟的每个单独的功能,然后对行为进行编程。所以:

 $.each = mockFunction();
 when($.each)(...matchers...).thenReturn(...);

但它仍然允许测试您需要的内容。

【讨论】:

    【解决方案2】:

    作为 alpian 答案的扩展,您可以创建 DOM 元素而无需将它们添加到页面中。让你的 JS 函数将相关元素作为参数:

    listGamesCallback : function(data, gameListSelectElem) {
        var gameList = $(gameListSelectElem);
        ...
    

    并像这样测试它们:

    var fakeSelect = $('<select>'),
        data = ...;
    
    listGamesCallback(data, fakeSelect[0]);
    
    equal(fakeSelect.find('option').length, 1, 'must have exactly 1 option');
    ...
    

    上面的最后一行代码用于 qUnit。取你需要的任何东西,关键是说你可以传递一个从未添加到页面中的 DOM 元素,然后使用 jQuery 调查该 DOM 元素以查找它是否被正确操作。

    【讨论】:

      【解决方案3】:

      不知道我是否理解你的意思,但如果你想为你的例子创建“数据”,这是我知道的方法:

          var data = [ { id : 1 , name : 'foo' } , { id : 2, name : 'bar' ] ​
      

      但是 - 如果你想创建一个选项列表,那么你的代码需要一些修复: 见http://jsfiddle.net/7MMap/

      var data = [ { gameId : 1 , name : 'foo' ,isJoinable:true} , { gameId : 2, name : 'bar' ,isJoinable:false}] 
      
          listGamesCallback = function(data) {
           var gameList = $("#gameList select")
                           .empty();
      
           $.each(data, function(i,d) {
                var newOption = $('<option>', {value : d.gameId })
                 .text(d.name)
                 .data("isJoinable", d.isJoinable);
      
                // Add it to the list
                gameList.append(newOption);
           })
                   };
      
      listGamesCallback(data);
      

      【讨论】:

      • 实际上,jQuery.each() 函数允许您直接使用 'this' 访问值。所有这些 .text().data() 使模拟变得复杂,恕我直言,它使其可读性降低。但这只是意见。感谢您格式正确的答案。不过,我仍然希望模拟 jQuery。
      【解决方案4】:

      Mocking jQuery 不是 mocking 的用途。你应该只嘲笑你对象的合作者。 jQuery 为您提供了一些实用程序——它不是协作者,因此不应被嘲笑。

      您在这里协作的是 DOM,或者是您的代码和 DOM 之间的一些中间对象。 data 是一个值对象,可以按照 Avi 的建议在您的测试中简单地创建。

      在我的 JS 测试中,我不模拟 DOM,我使用真实的 DOM,并且肯定会拆除我在测试之间创建的任何内容,这似乎工作得很好。

      【讨论】:

      • 我同意我在这种情况下的过度测试。但是,我的想法是,由于我希望我的测试尽可能孤立,我不希望 jQuery 实际上做任何事情,我只需要验证它是否被使用。但是,这确实要求始终使用 jQuery 来通过测试。所以你说得有道理。
      • 当然你可以,但在你的情况下,我实际上只是在你的测试中,将一个带有 id gameList 的 REAL select 添加到 REAL DOM,然后运行你的函数,然后在它运行之后证明(比如使用 CSS 选择器)DOM 看起来应该是它应该的方式 - 然后通过删除该选择自行清理。忘记尝试模拟 jQuery - 它是一个实用程序 - 你不应该这样做:)。
      • 所以当你想测试对jQuery.ajax的调用时,你应该设置一个假服务器吗?我不认为模拟 jQuery 有什么问题——你所说的相当于“不需要模拟你的 DAL 实现,只需创建一个测试数据库”。这是集成测试,不是单元测试
      • jQuery 是一个拥有大量工具集的 API。其中一些工具是实用程序,有些是服务。显然,您会在单元测试中模拟 Ajax 调用。 OP 询问的是在 jQuery 中模拟实用程序函数,这是一个完全不同的问题。
      • 使用 DOM 进行测试时,主要是集成或端到端测试,不一定是单元测试。模拟任何您希望不会随着时间而频繁更改的东西,并且在您的代码之外(例如,对于一个函数的单元测试,该函数之外的任何东西都是外部的)可以被模拟(并且应该是,假设代码本身很好)测试)。所以 mocking jQuery 没有错。
      猜你喜欢
      • 2021-08-04
      • 2010-09-29
      • 2019-01-30
      • 2014-01-18
      • 1970-01-01
      • 2013-05-26
      • 1970-01-01
      • 1970-01-01
      • 2015-10-17
      相关资源
      最近更新 更多