【问题标题】:Unit testing AJAX requests with QUnit使用 QUnit 对 AJAX 请求进行单元测试
【发布时间】:2012-02-24 13:31:26
【问题描述】:

我们正在尝试为重 JS 的网络应用程序实现 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。 Check this link

标签: javascript unit-testing qunit


【解决方案1】:

使用 jQuery,您可以使用 .ajax() 作为承诺返回的 xhr 对象,因此您可以添加更多的处理程序(见下文),而不仅仅是您定义的单个 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() 回调。

【讨论】:

  • 这很棒。非常感谢:)
【解决方案2】:

如果它是一个可以(并且应该)独立于服务器端运行的单元测试,您可以简单地“替换”$.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 逻辑放在它自己的方法中,这样您就可以在测试期间轻松地存根它。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-19
  • 2018-03-13
  • 2010-11-16
相关资源
最近更新 更多