javascript 使用 QUnit 对 AJAX 请求进行单元测试
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9431597/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Unit testing AJAX requests with QUnit
提问by James Allardice
We are trying to implement QUnit JavaScript tests for a JS-heavy web app. We are struggling to find a way to successfully test methods that involve jQuery AJAX requests. For example, we have the following constructor function (obviously this is a very simplistic example):
我们正在尝试为 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();
We are trying to find a way to test the fire
method, preferably with a stubbed URL instead of the real someURL.php
.
我们正在尝试找到一种方法来测试该fire
方法,最好使用存根 URL 而不是真实的someURL.php
.
The only obvious solution to me at the moment is add the URL, and the success
callback, as arguments to the constructor function. That way, in the test, we can create a new instance of X
and pass in the stub URL, and a callback to run when the stub returns a response. For example:
目前对我来说唯一明显的解决方案是添加 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();
});
});
However, this doesn't seem like a very nice solution. Is there a better way?
然而,这似乎不是一个很好的解决方案。有没有更好的办法?
回答by Flambino
With jQuery, you can use the xhr object that .ajax()
returns as a promise, so you can add more handlers (see below) than just the single success
, complete
and error
ones you define in the options. So if your async function can return the xhr object, you can add test-specific handlers.
使用 jQuery,您可以使用.ajax()
作为承诺返回的 xhr 对象,因此您可以添加更多处理程序(见下文),而不仅仅是单个success
,complete
以及error
您在选项中定义的处理程序。因此,如果您的异步函数可以返回 xhr 对象,则您可以添加特定于测试的处理程序。
As for the URL, that's a little trickier. I've sometimes set up a very simple Node server on localhost, which just serves canned responses that were copied from the real server. If you run your test suite off that same server, your URLs just need to be absolute paths to hit the test server instead of the production server. And you also get a record of the requests themselves, as a server sees them. Or you can have the test server send back errors or bad responses on purpose, if you want to see how the code handles it.
至于网址,那就有点棘手了。我有时会在本地主机上设置一个非常简单的 Node 服务器,它只提供从真实服务器复制的罐头响应。如果您在同一台服务器上运行测试套件,则您的 URL 只需要是访问测试服务器而不是生产服务器的绝对路径。而且您还可以获得请求本身的记录,就像服务器看到的那样。或者,如果您想查看代码如何处理它,您可以让测试服务器故意发回错误或错误响应。
But that's of course a pretty complex solution. The easier one would be to define your URLs in a place where you can redefine them from the test suite. For instance:
但这当然是一个非常复杂的解决方案。更简单的方法是在可以从测试套件中重新定义它们的地方定义您的 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
Also, QUnit has an asyncTest
function, which calls stop()
for you. Add a tiny helper to keep track of when to start again, and you've got a pretty good solution.
此外,QUnit 有一个asyncTest
函数,它会调用stop()
你。添加一个小助手来跟踪何时重新开始,您就有了一个很好的解决方案。
Here's what I've done before
这是我以前做过的
// 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
});
Basically countDown
is a function that counts down to zero from whatever you specify, and then calls start()
. In this case, there's 1 async call, so countDown
will count down from that. And it'll do so when the ajax call finishes, regardless of how it went, since it's set up as an always
callback.
And because the asyncTest
is told to expect 2 assertions, it'll report an error if the .done()
callback is never called, since no assertions will be run. So if the call completely fails, you'll know that too. If you want to log something on error, you can add a .fail()
callback to the promise chain.
基本上countDown
是一个函数,它从您指定的任何内容倒计时到零,然后调用start()
. 在这种情况下,有 1 个异步调用,因此countDown
将从中倒计时。它会在 ajax 调用完成时这样做,不管它如何进行,因为它被设置为always
回调。
并且因为asyncTest
被告知需要 2 个断言,所以如果.done()
从未调用回调,它将报告错误,因为不会运行任何断言。因此,如果调用完全失败,您也会知道这一点。如果你想记录一些错误,你可以.fail()
在承诺链中添加一个回调。
回答by Iazel
If it's a unit test that can (and should) be run in isolation from the server side, you can simply "replace" $.ajax
to simulate whatever behavior.
One easy example:
如果它是一个可以(并且应该)与服务器端隔离运行的单元测试,您可以简单地“替换”$.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;
});
Obviously you can also perform a real ajax call with stubbed url/data:
显然,您还可以使用存根 url/data 执行真正的 ajax 调用:
// Simulate a successfully response
$.ajax = function(url, opts) {
opts.success = function(data) {
console.log(data);
start();
}
_real_ajax('stub.php', opts)
}
If you haven't a complex response, I prefer the first approach, because it is faster and easy to comprehend.
However, you can also take another way and put the Ajax logic in it's own method, so you can easily stub it during tests.
如果您没有复杂的响应,我更喜欢第一种方法,因为它更快且易于理解。
但是,您也可以采用另一种方式,将 Ajax 逻辑放在它自己的方法中,以便在测试期间轻松地存根。