2012-02-24 196 views
6

我们正在尝试为JS沉重的Web应用程序实施QUnit JavaScript测试。我们正在努力寻找一种方法来成功测试涉及jQuery AJAX请求的方法。例如,我们有以下的构造函数(显然这是一个非常简单的例子):使用QUnit单元测试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(); 
    }); 
}); 

但是,这似乎不是一个很好的解决方案。有没有更好的办法?

回答

9

使用jQuery,您可以使用XHR对象.ajax()回报的承诺,这样你就可以添加更多的处理程序(见下文),不只是你的选项定义了单一successcompleteerror的。所以如果你的异步函数可以返回xhr对象,你可以添加特定于测试的处理程序。

至于URL,这有点棘手。我有时在本地主机上设置了一个非常简单的节点服务器,它只提供从真实服务器复制的预设回应。如果您从同一台服务器上运行测试套件,那么您的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()回调从未被调用,它将报告错误,因为没有断言将被运行。所以如果通话完全失败,你也会知道。如果您想记录错误信息,可以将.fail()回调添加到承诺链中。

+0

这太好了。非常感谢:) – 2012-02-24 15:27:13

2

尝试使用jQuery spy。我创建了一个更简单的方法来使用jQuery熟悉的语法来测试ajax。

Check this link

3

如果它是可以(也应该)被孤立于服务器端运行单元测试,你可以简单地“代替” $.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 /数据进行真正的Ajax调用:

// Simulate a successfully response 
$.ajax = function(url, opts) { 
    opts.success = function(data) { 
    console.log(data); 
    start(); 
    } 
    _real_ajax('stub.php', opts) 
} 

如果您还没有一个复杂的反应,我更喜欢第一种方法,因为它更快更容易理解。
但是,您也可以采取另一种方式,并将Ajax逻辑放入其自己的方法中,以便在测试期间轻松存根。

+0

为了从服务器端隔离我的测试,我用这个库取得了很大的成功https://github.com/jakerella/jquery-mockjax – 2014-12-15 19:23:27