如何使用Sinon和QUnit对$(function(){...})块进行单元测试?

时间:2012-11-25 02:48:42

标签: jquery unit-testing qunit sinon

我有一个像这样的JS文件中的代码(当然简化):

$(function () {
  var num;
  $.getJSON('./getNumber.php', function (n) {
    num = n;
  });

  $('#id').on('click', function () { alert(num); });
});

我需要编写两个单元测试:

  1. 确保脚本在页面加载时向服务器发送./getNumber.php请求
  2. 确保点击#id时,num会收到提醒。
  3. 显然我需要FakeXmlHTTPRequest并模拟alert函数,甚至可能窥探$.getJSON。但是我不确定在保持两个原子的同时编写测试的正确方法是什么。

    我认为唯一这样做会为每次测试动态注入<script>块;但我觉得这不对。什么是正确的方法?感谢。

    编辑:根据SO之外的评论,我需要学习的是编写可测试的Javascript,而不是尝试为测试用例提供低测试性的测试用例。如果有人能给我一些关于重写这段代码的建议,也会非常感激。

1 个答案:

答案 0 :(得分:2)

你的问题没有一个正确的答案,而是要学习很多好的原则。请考虑以下代码。我重构了你的例子。我将解释我在下面所做的一切。

// Define your module in a self-executing function.
// This module is now completely unit testable.
var myModule = (function () {
    var num;

    function getNum() {
        $.getJSON('./getNumber.php', function (n) {
            num = n;
        });
    }

    function alertNum() {
        alert(num);
    }

    // this returned object will by set to myModule and will publicly
    // expose the necessary functions
    return {
        getNum: getNum,
        alertNum: alertNum
    };
})();

// wire up your events
$(function() {
    $("#id").on('click', myModule.alertNum);
});

对于这个简单的代码来说,这个例子显然有些过分,但它应该会给你一些好的想法。

  1. 您的核心功能现已封装在模块中。您在模块中保留了其他私有函数和变量(如num),但现在可以测试通过返回对象公开公开的所有内容。 (见下面的注释)

  2. 我已将模块外的事件连接起来。如果您的模块很大,您甚至可能希望将此代码放在另一个脚本中。无论哪种方式,它都会以类似MVC的方式将您的关注点分开。你可以测试这段代码,但你真的不应该这样做;它应该是非常简单的jQuery,并且围绕它进行包装测试只不过是测试jQuery库,jQuery库已经过jQuery团队的全面测试。

  3. 这可能看起来很奇怪,但不要在模块中使用任何jQuery选择器!如果您的模块需要一个元素,请将其作为函数参数传递。有一些方法可以将这些东西排除在外,但这真的很麻烦,如果你已经正确地分离了你的顾虑,那就没有必要了。我的大部分模块(或其中的对象)都采用init()方法,我可以传入内容。 - 您可能仍然选择在模块内的元素上使用jQuery的.find()方法。这只是一个判断调用,只有你可以根据模块的特殊需求做出。这样做会要求你在测试中做更详细的DOM元素模拟,但有时候只需要将几十个元素传递到init()(只需传入主容器)。


  4. 注意:我强烈建议使用RequireJS或其他AMD模块加载程序来管理模块。这将使myModule之类的内容远离全局命名空间。但是设置QUnit需要有点不耐烦,一旦你掌握了单元测试,可能会遇到一个问题。