四、异步测试——AJAX
不可避免地,在每个 JavaScript 应用中都有需要测试异步代码的时候。
异步意味着你不能以线性方式处理它——一个函数可能在执行后立即返回,但是结果会在稍后出现,通常是通过回调。
这是处理 AJAX 请求时非常常见的模式,例如通过 jQuery:
$.ajax('http://0.0.0.0/data.json', {
success: function (data) {
// handle the result
}
});
为了演示 Jasmine 对异步测试的支持,我们将实现以下验收标准:
"股票回笼时,应更新其股价."
通过使用我们到目前为止向您展示的技术,您可以在spec/StockSpec.js
文件中编写如下验收标准:
describe("when fetched", function() {
beforeEach(function() {
stock.fetch();
});
it("should update its share price", function() {
expect(stock.sharePrice).toEqual(23.67);
});
});
这将导致在src/Stock.js
文件中实现fetch
功能:
Stock.prototype.fetch = function() {
var that = this;
var url = 'http://0.0.0.0:8000/stocks/'+that.symbol;
$.getJSON(url, function (data) {
that.sharePrice = data.sharePrice;
});
};
重要的部分是$.getJSON
调用,这是一个 AJAX 请求,期待包含更新股价的 JSON 响应:
{
"sharePrice": "23.67"
}
到现在,你可以看到我们被卡住了;为了运行这个规范,我们需要一个运行的服务器。
设置场景
由于这本书完全是关于 JavaScript 的,我们将创建一个非常简单的 Node.js 服务器供规范使用。Node.js 是一个允许使用 JavaScript 开发网络应用的平台,比如 web 服务器。
在第 6 章、光速单元测试中,我们将看到在不需要服务器的情况下测试 AJAX 请求的替代解决方案。
安装 Node.js
如果已经安装了 Node.js,可以跳到下一节。
有适用于 Windows 和 Mac OS X 的安装程序:
- 前往 Node.js 网站http://nodejs.org/
- 点击安装按钮
- 下载完成后,执行安装程序并按照步骤操作
要查看其他安装方法和如何在 Linux 发行版上安装的说明,请查看https://github . com/joyent/node/wiki/Installing-node . js-via-package-manager上的官方文档。
完成后,您的命令行上应该有一个node
命令。
对服务器进行编码
为了学习如何编写异步规范,我们将创建一个返回一些假数据的服务器。在项目的根文件夹中创建一个名为server.js
的新文件,并添加其内容:
var express = require('express');
var app = express();
app.get('/stocks/:symbol', function (req, res) {
res.setHeader('Content-Type', 'application/json');
res.send({ sharePrice: 20.18 });
});
app.use(express.static(__dirname));
app.listen(8000);
为了处理 HTTP 请求,我们使用了快速,一个 Node.js 网络应用框架。通过阅读代码,可以看到它定义了一个到/stocks/:symbol
的路由,所以它接受http://0.0.0.0:8000/stocks/AOUE
等请求,并用 JSON 数据进行响应。
我们还使用express.static
模块为http://0.0.0.0:8000/SpecRunner.html
的规范转轮提供服务。
有规避 同产地政策的要求。出于安全原因,这是一个规定 AJAX 请求不允许在不同于应用的域上执行的策略。
这个问题是在第三章、测试前端代码中使用带有 Chrome 浏览器的 HTML 装置时首次演示的,因为在处理file://
网址时,有些浏览器的要求比其他浏览器更严格。
使用 Chrome 浏览器检查器,你可以在打开带有file://
协议的SpecRunner.html
文件时看到控制台中的错误(基本上是你一直在做的事情):
同源策略错误
通过服务于 runner,以及同一基本 URL 下的所有应用和测试代码,我们防止了这个问题的发生,并且能够在任何浏览器上运行规范。
运行服务器
要运行服务器,首先需要使用节点的包管理器安装其依赖项(快速)。在应用根文件夹中,运行npm
命令:
$ npm install express
该命令将下载 Express,并将其放入项目文件夹中名为node_modules
的新文件夹中。
现在,您应该能够通过调用node
命令来运行服务器:
$ node server.js
要检查它是否工作,请在浏览器上点击http://0.0.0.0:8000/stocks/AOUE
,您应该会收到 JSON 响应:
{
"sharePrice": "23.66"
}
现在我们已经使我们的服务器依赖性工作了,我们可以继续编写规范了。
编写规范
在服务器运行的情况下,在http://0.0.0.0:8000/SpecRunner.html
打开浏览器,查看我们的规范结果。
您可以看到,即使服务器正在运行,并且规范看起来是正确的,但它还是失败了。这是因为stock.fetch()
是异步的。对stock.fetch()
的调用立即返回,允许 Jasmine 在 AJAX 请求完成之前运行期望:
it("should update its share price", function() {
expect(stock.sharePrice).toEqual(23.67);
});
为了解决这个问题,我们需要接受stock.fetch()
函数的异步性,并指示 Jasmine 在运行期望之前等待它的执行。
等待时间()函数
要告诉 Jasmine 等待异步调用,我们需要使用它的另一个全局函数waitsFor()
。
在深入了解它的工作原理之前,让我们先来修改一下之前的测试代码,以使用这个新函数:
describe("when fetched", function() {
var fetched = false;
beforeEach(function() {
stock.fetch({
success: function () {
fetched = true;
}
});
waitsFor(function (argument) {
return fetched;
}, 'Timeout fetching stock data', 2000);
});
it("should update its share price", function() {
expect(stock.sharePrice).toEqual(23.67);
});
});
您首先会注意到,我们在stock.fetch()
函数中添加了一个success
回调,在获取完成后将fetched
变量设置为true
:
stock.fetch({
success: function () {
fetched = true;
}
});
其实施如下:
Stock.prototype.fetch = function(parameters) {
var that = this;
var params = parameters || {};
var success = params.success || function () {};
var url = 'http://0.0.0.0:8000/stocks/'+that.symbol;
$.getJSON(url, function (data) {
that.sharePrice = data.sharePrice;
success(that);
});
};
然后我们使用waitsFor()
功能保持it
块的执行,直到fetched
变量为true
:
waitsFor(function (argument) {
return fetched;
}, 'Timeout fetching stock data', 2000);
如果股票没有在 2000 毫秒内被取出,它会抛出一个错误,使规范失败。
让我们回顾一下,waitsFor()
功能接受三个参数:
-
A function that Jasmine will poll, until it gets a truth result:
function (argument) { return fetched; }
-
An error message to show if the waiting times out:
"提取股票数据超时"
-
超时前等待的时间(毫秒):
2000
。
因此,每当您有任何依赖于异步调用结果的期望时,您可以通过使用beforeEach
块中的waitsFor()
函数来保持它的执行。
接下来,我们将看到如何直接在it
块内部使用waitsFor()
功能。
运行()功能
我们已经看到我们可以使用beforeEach
中的waitsFor()
函数,但是如果我们需要编写一个在it
块中有异步调用的测试代码呢?
作为练习,让我们重写之前的规范,不将其嵌套在describe
块中,而是将其作为单个it
块:
it("should be able to update its share price", function() {
var fetched = false;
stock.fetch({
success: function() {
fetched = true;
}
});
waitsFor(function (argument) {
return fetched;
}, 'Timeout fetching stock data', 2000);
expect(stock.sharePrice).toEqual(23.67);
});
通过运行这个例子,你会发现同步问题又回来了。那是因为waitsFor()
函数并没有阻止执行。
它以前是有效的,因为 Jasmine 等待运行it
块,直到waitsFor()
完成。
所以我们需要一种方法来安排这个期望代码在waitsFor()
完成后运行。正如你可能已经猜到的,这将是通过另一个 Jasmine 全局函数,runs
函数。
您所要做的就是在runs
块中移动您想要尊重异步行为的代码:
it("should be able to update its share price", function() {
var fetched = false;
stock.fetch({
success: function() {
fetched = true;
}
});
waitsFor(function (argument) {
return fetched;
}, 'Timeout fetching stock data', 2000);
runs(function() {
expect(stock.sharePrice).toEqual(23.67);
});
});
这样,Jasmine 只在waitsFor()
完成后运行该代码。
您甚至可以放置多个runs
块,它们将按照声明的顺序运行:
it("should be able to update its share price", function() {
var fetched = false;
runs(function() {
stock.fetch({
success: function() {
fetched = true;
}
});
});
waitsFor(function (argument) {
return fetched;
}, 'Timeout fetching stock data', 2000);
runs(function() {
expect(stock.sharePrice).toEqual(23.67);
});
runs(function() {
expect(stock.sharePrice).not.toBeUndefined();
});
runs(function() {
expect(stock.sharePrice).toBeGreaterThan(0);
});
});
总结
在本章中,您已经看到了如何测试异步代码,这是测试服务器交互(AJAX)时常见的场景。我已经向您展示了如何使用不同的 Jasmine 全局函数,如waitsFor()
和runs
来测试异步代码。
我还向您展示了 Node.js 平台,并使用它编写了一个简单的服务器代码,用作测试设备。
在第 6 章、光速单元测试中,我们将看到 AJAX 测试的不同解决方案,不需要服务器运行的解决方案。
版权属于:月萌API www.moonapi.com,转载请注明出处