总体概述
jasmine-ajax主要五个对象组成:
- 请求伪造对象(FackXMLHttpRequest):
function FakeXMLHttpRequest() {}
- 请求跟踪对象(RequestTracker):
function RequestTracker() {}
- 请求拦截对象(
RequestStub
):用作对伪造的AJAX请求做出响应。
function RequestStub(url, stubData, method) {}
- 请求拦截跟踪对象(StubTracker):
function StubTracker() {}
- 参数解析(ParamParser):
function ParamParser() {}
辅助函数
arrayContains
function arrayContains(arr, item) {
for (var i = 0; i < arr.length; i++) {
if (arr[i] === item) {
return true;
}
}
return false;
}
函数结构很明显,判定一个数组是否包含某一个值。
extend
function extend(destination, source, propertiesToSkip) {
propertiesToSkip = propertiesToSkip || [];
for (var property in source) {
if (!arrayContains(propertiesToSkip, property)) {
destination[property] = source[property];
}
}
return destination;
}
此函数实现对象扩展,propertiesToSkip
参数为source
中需排除掉的键,余下的键值对覆盖写入destination
。
初始化语句
if (typeof window === "undefined" && typeof exports === "object") {
exports.MockAjax = MockAjax;
jasmine.Ajax = new MockAjax(exports);
} else {
window.MockAjax = MockAjax;
jasmine.Ajax = new MockAjax(window);
}
首先判定是否commonjs
环境,不是则传递window
变量。在describe
内部,即可访问通过jasmine.Ajax
使用MockAjax对象。
RequestTracker
其实结构非常明显,使用jasmine.Ajax.requests即可访问到RequestTracker对象,然后通过first, mostRecent, at, filter 等方法获取仿造的request对象,多用于对HTTP请求对象进行判定,如方法,路径,数据,headers等等,需要重点关注。reset, track手动调用意义不大,前者用于重置变量环境,后者在生成伪造XHR时调用。
function RequestTracker() {
var requests = [];
this.track = function(request) {
requests.push(request);
};
this.first = function() {
return requests[0];
};
this.count = function() {
return requests.length;
};
this.reset = function() {
requests = [];
};
this.mostRecent = function() {
return requests[requests.length - 1];
};
this.at = function(index) {
return requests[index];
};
this.filter = function(url_to_match) {
if (requests.length === 0) { return []; }
var matching_requests = [];
for (var i = 0; i < requests.length; i++) {
if (url_to_match instanceof RegExp &&
url_to_match.test(requests[i].url)) {
matching_requests.push(requests[i]);
} else if (url_to_match instanceof Function &&
url_to_match(requests[i])) {
matching_requests.push(requests[i]);
} else {
if (requests[i].url === url_to_match) {
matching_requests.push(requests[i]);
}
}
}
return matching_requests;
};
}
StubTracker
通过jasmine.Ajax.stubs
即可访问到StubTracker
对象,主动调用意义不大。reset
方法用于变量环境重置,addStub
方法在生成RequestStub
对象时调用。findStub
在xhr.open()
调用时,判定是否定义过response
。
function StubTracker() {
var stubs = [];
this.addStub = function(stub) {
stubs.push(stub);
};
this.reset = function() {
stubs = [];
};
this.findStub = function(url, data, method) {
for (var i = stubs.length - 1; i >= 0; i--) {
var stub = stubs[i];
if (stub.matches(url, data, method)) {
return stub;
}
}
};
}
RequestStub
请求拦截在url, stubData, method同时匹配的情况下才会启动拦截。
function RequestStub(url, stubData, method) {
var normalizeQuery = function(query) {
return query ? query.split('&').sort().join('&') : undefined;
};
if (url instanceof RegExp) {
this.url = url;
this.query = undefined;
} else {
var split = url.split('?');
this.url = split[0];
this.query = split.length > 1 ? normalizeQuery(split[1]) : undefined;
}
this.data = normalizeQuery(stubData);
this.method = method;
this.andReturn = function(options) {
this.status = options.status || 200;
this.contentType = options.contentType;
this.responseText = options.responseText;
};
this.matches = function(fullUrl, data, method) {
var matches = false;
fullUrl = fullUrl.toString();
if (this.url instanceof RegExp) {
matches = this.url.test(fullUrl);
} else {
var urlSplit = fullUrl.split('?'),
url = urlSplit[0],
query = urlSplit[1];
matches = this.url === url && this.query === normalizeQuery(query);
}
return matches && (!this.data || this.data === normalizeQuery(data)) && (!this.method || this.method === method);
};
}
ParamParser
主要负责值的解析。paramParsers参数为parser对象数组。findParser遍历paramParsers,通过test属性判定是否启用对应的parse方法处理数据。若决定调用,则遍历结束,返回该对象。add方法一般会通过jasmine.Ajax.addCustomParamParser(parser)调用,优先级上,后添加的>先添加的>默认的。
function ParamParser() {
var defaults = [
{
test: function(xhr) {
return (/^application\/json/).test(xhr.contentType());
},
parse: function jsonParser(paramString) {
return JSON.parse(paramString);
}
},
{
test: function(xhr) {
return true;
},
parse: function naiveParser(paramString) {
var data = {};
var params = paramString.split('&');
for (var i = 0; i < params.length; ++i) {
var kv = params[i].replace(/\+/g, ' ').split('=');
var key = decodeURIComponent(kv[0]);
data[key] = data[key] || [];
data[key].push(decodeURIComponent(kv[1]));
}
return data;
}
}
];
var paramParsers = [];
this.add = function(parser) {
paramParsers.unshift(parser);
};
this.findParser = function(xhr) {
for(var i in paramParsers) {
var parser = paramParsers[i];
if (parser.test(xhr)) {
return parser;
}
}
};
this.reset = function() {
paramParsers = [];
for(var i in defaults) {
paramParsers.push(defaults[i]);
}
};
this.reset();
}
FackXMLHttpRequest
代码过长,分段说明。
- FakeXMLHttpRequest
实例化后,即添加进入requestTracker,便于后期访问。
function FakeXMLHttpRequest() {
requestTracker.track(this);
this.requestHeaders = {};
this.overriddenMimeType = null;
}
- 原型继承
此处感觉比较好玩,是继承真的XMLHttpRequest对象,只是去掉几个特殊键值对,后面会进行处理。
var iePropertiesThatCannotBeCopied = ['responseBody', 'responseText', 'responseXML', 'status', 'statusText', 'responseTimeout'];
extend(FakeXMLHttpRequest.prototype, new window.XMLHttpRequest(), iePropertiesThatCannotBeCopied);
- Request伪造
熟悉原生ajax的应该很容易看懂。重点在于,send方法调用时,会立即在stubs里寻找匹配当前url,method,data的响应拦截器,如果预定义过了,则会立刻传递给response函数进行响应,否则需要之后手动调用response函数进行响应。
extend(FakeXMLHttpRequest.prototype, {
open: function() {
this.method = arguments[0];
this.url = arguments[1];
this.username = arguments[3];
this.password = arguments[4];
this.readyState = 1;
this.onreadystatechange();
},
setRequestHeader: function(header, value) {
if(this.requestHeaders.hasOwnProperty(header)) {
this.requestHeaders[header] = [this.requestHeaders[header], value].join(', ');
} else {
this.requestHeaders[header] = value;
}
},
overrideMimeType: function(mime) {
this.overriddenMimeType = mime;
},
abort: function() {
this.readyState = 0;
this.status = 0;
this.statusText = "abort";
this.onreadystatechange();
},
readyState: 0,
onload: function() {
},
onreadystatechange: function(isTimeout) {
},
status: null,
send: function(data) {
this.params = data;
this.readyState = 2;
this.onreadystatechange();
var stub = stubTracker.findStub(this.url, data, this.method);
if (stub) {
this.response(stub);
}
},
contentType: function() {
return findHeader('content-type', this.requestHeaders);
},
data: function() {
if (!this.params) {
return {};
}
return paramParser.findParser(this).parse(this.params);
},
getResponseHeader: function(name) {
return findHeader(name, this.responseHeaders);
},
getAllResponseHeaders: function() {
var responseHeaders = [];
for (var i in this.responseHeaders) {
if (this.responseHeaders.hasOwnProperty(i)) {
responseHeaders.push(i + ': ' + this.responseHeaders[i]);
}
}
return responseHeaders.join('\r\n');
},
responseText: null,
response: function(response) {
if (this.readyState === 4) {
throw new Error("FakeXMLHttpRequest already completed");
}
this.status = response.status;
this.statusText = response.statusText || "";
this.responseText = response.responseText || "";
this.readyState = 4;
this.responseHeaders = response.responseHeaders ||
{"Content-Type": response.contentType || "application/json" };
this.onload();
this.onreadystatechange();
},
responseTimeout: function() {
if (this.readyState === 4) {
throw new Error("FakeXMLHttpRequest already completed");
}
this.readyState = 4;
jasmine.clock().tick(30000);
this.onreadystatechange('timeout');
}
});
return FakeXMLHttpRequest;
}
经验交流
QQ: 491229492
Email: huang.jian@eisoo.com
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。