使用QUnit对AJAX请求进行单元测试

8

我们正在尝试为一个JS重度的Web应用实现QUnit JavaScript测试。我们正在努力找到一种成功测试涉及jQuery AJAX请求的方法。例如,我们有以下构造函数(显然这只是一个非常简单的例子):

var X = function() {
    this.fire = function() {
        $.ajax("someURL.php", {
            data: {
                userId: "james"
            },
            dataType: "json",
            success: function(data) {
                //Do stuff
            }
        });
    };
};
var myX = new X();
myX.fire();

我们正在寻找一种测试fire方法的方法,最好使用存根URL而不是真正的someURL.php。目前我唯一明显的解决方案是将URL和success回调作为构造函数的参数添加。这样,在测试中,我们可以创建一个新的X实例,并传入存根URL和当存根返回响应时要运行的回调。例如:
test("Test AJAX function", function() {
    stop();
    var myX = new X();
    //Call the AJAX function, passing in the stub URL and success callback
    myX.fire("stub.php", function(data) {
        console.log(data);
        start();
    });
});

然而,这似乎不是一个很好的解决方案。有更好的方法吗?

尝试使用 jQuery spy 。我已经创建了一种更简单的方式,可以使用 jQuery 熟悉的语法来测试 ajax。查看此链接 - steve
2个回答

10
使用jQuery,您可以使用.ajax()返回的xhr对象作为Promise,并添加更多处理程序(请参见下文),而不仅仅是在选项中定义的单个successcompleteerror。因此,如果您的异步函数可以返回xhr对象,则可以添加特定于测试的处理程序。
至于URL,那就有点棘手了。我有时会在localhost上设置一个非常简单的Node服务器,它只提供从真实服务器复制的预设响应。如果您从同一台服务器运行测试套件,则您的URL只需要是绝对路径,以命中测试服务器而不是生产服务器。您还可以获得请求本身的记录,就像服务器看到的那样。或者,如果您想查看代码处理错误或错误响应的方式,则可以让测试服务器故意发送错误或错误响应。
但这当然是一个相当复杂的解决方案。更简单的方法是在可以重新定义它们的位置定义您的URL。例如:
/* in your code */
var X = function () {
    this.fire = function () {
        return $.ajax({ url: this.constructor.url, ... });
    };
};
X.url = "someURL.php"; // the production url

/* in your tests */
X.url = "stub.php"; // redefine to the test url

另外,QUnit提供了一个asyncTest函数,它会自动调用stop()。添加一个小助手来跟踪何时重新开始,你就有了一个相当不错的解决方案。

以下是我先前所做的事情:

// create a function that counts down to `start()`
function createAsyncCounter(count) {
    count = count || 1; // count defaults to 1
    return function () { --count || start(); };
}

// ....

// an async test that expects 2 assertions
asyncTest("testing something asynchronous", 2, function() {
    var countDown = createAsyncCounter(1), // the number of async calls in this test
        x = new X;

    // A `done` callback is the same as adding a `success` handler
    // in the ajax options. It's called after the "real" success handler.
    // I'm assuming here, that `fire()` returns the xhr object
    x.fire().done(function(data, status, jqXHR) {
        ok(data.ok);
        equal(data.value, "foobar");
    }).always(countDown); // call `countDown` regardless of success/error
});

基本上,countDown是一个函数,从你指定的数字开始倒数到零,然后调用start()。在这种情况下,有1个异步调用,所以countDown将从该调用开始倒数。无论异步调用的结果如何,它都会在Ajax调用完成时进行计数,因为它被设置为一个always回调函数。
由于asyncTest被告知期望2个断言,如果.done()回调函数没有被调用,它将报告一个错误,因为没有任何断言被运行。因此,如果调用完全失败,你也会知道。如果你想在错误时记录一些东西,可以在Promise链中添加一个.fail()回调函数。


4

如果这是一个单元测试,可以(而且应该)在不涉及服务端的情况下运行,你可以简单地“替换”$.ajax以模拟任何行为。 一个简单的示例:

test("Test AJAX function", function() {
  // keep the real $.ajax
  var _real_ajax = $.ajax;

  // Simulate a successful response
  $.ajax = function(url, opts) {
    opts.success({expected: 'response'});
  }

  var myX = new X();
  // Call your ajax function
  myX.fire();
  // ... and perform your tests

  // Don't forgot to restore $.ajax!
  $.ajax = _real_ajax;
});

显然,您也可以使用存根的url/data执行真正的ajax调用:
// Simulate a successfully response
$.ajax = function(url, opts) {
  opts.success = function(data) { 
    console.log(data);
    start();
  }
  _real_ajax('stub.php', opts)
}

如果你不需要一个复杂的响应,我建议采用第一种方法,因为它更快且易于理解。
然而,你也可以采取另一种方式,将Ajax逻辑放在它自己的方法中,这样你就可以在测试期间轻松地进行存根。


1
为了将我的测试与服务器端隔离开来,我使用了这个库 https://github.com/jakerella/jquery-mockjax,并且取得了很大的成功。 - Adam Waselnuk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接