所以我刚刚开始使用sinon.js
&为我正在进行的 javascript 应用程序编写测试jasmine.js
。总体上工作得很好,但我还需要能够测试我的路由器。
路由器在其当前状态下将触发许多视图和其他内容,jasmine.js
通过调用Backbone.navigate
依赖于应用程序状态和 UI 迭代来终止当前测试。
那么我如何测试到不同位置的路由是否可行,同时保持路由器“沙盒”并且不允许它们更改路由?
我可以设置某种模拟函数来监视 pushState 更改或类似的吗?
所以我刚刚开始使用sinon.js
&为我正在进行的 javascript 应用程序编写测试jasmine.js
。总体上工作得很好,但我还需要能够测试我的路由器。
路由器在其当前状态下将触发许多视图和其他内容,jasmine.js
通过调用Backbone.navigate
依赖于应用程序状态和 UI 迭代来终止当前测试。
那么我如何测试到不同位置的路由是否可行,同时保持路由器“沙盒”并且不允许它们更改路由?
我可以设置某种模拟函数来监视 pushState 更改或类似的吗?
这是使用 jasmine 执行此操作的低级方法,测试 pushState 是否按预期工作以及您的路由器是否正确router
设置... 您可以将此调整为您的其他路线。我还假设您已经在应用程序初始化中完成了Backbone.history.start({ pushState: true });
describe('app.Router', function () {
var router = app.router, pushStateSpy;
it('has a "home" route', function () {
expect(router.routes['']).toEqual('home');
});
it('triggers the "home" route', function () {
var home = spyOn(router, 'home').andCallThrough();
pushStateSpy = spyOn(window.history, 'pushState').andCallFake(function (data, title, url) {
expect(url).toEqual('/');
router.home();
});
router.navigate('');
expect(pushStateSpy).toHaveBeenCalled();
expect(home).toHaveBeenCalled();
...
});
});
Backbone.history.stop();
出于这个原因,您可以通过这样做来有效地实现类似的事情。
更新:没有的浏览器pushState
:
如果您测试的浏览器支持pushState
. 如果您针对不支持的浏览器进行测试,则可以有条件地进行如下测试:
it('triggers the "home" route', function () {
var home = spyOn(router, 'home').andCallThrough();
if (Backbone.history._hasPushState) {
pushStateSpy = spyOn(window.history, 'pushState').andCallFake(function (data, title, url) {
expect(url).toEqual('/');
router.home();
});
router.navigate('', {trigger: true});
expect(pushStateSpy).toHaveBeenCalled();
expect(home).toHaveBeenCalled();
} else if (Backbone.history._wantsHashChange) {
var updateHashSpy = spyOn(Backbone.history, '_updateHash').andCallFake(function (loc, frag) {
expect(frag).toEqual('');
router.home();
});
router.navigate('', {trigger: true});
expect(updateHashSpy).toHaveBeenCalled();
expect(home).toHaveBeenCalled();
}
});
如果你在 IE6 上,祝你好运。
当我测试骨干路由器时,我关心的是我提供的路由是否调用了我使用正确参数指定的函数。这里的许多其他答案并没有真正测试这一点。
如果您需要测试某些路由的功能,您可以自己测试这些功能。
假设你有一个简单的路由器:
App.Router = Backbone.Router.extend({
routes: {
'(/)':'index',
'/item/:id':'item'
},
index: {
//render some template
},
item: {
//render some other template, or redirect, or _whatever_
}
});
这是我的做法:
describe('Router', function() {
var trigger = {trigger: true};
var router
beforeEach(function() {
// This is the trick, right here:
// The Backbone history code dodges our spies
// unless we set them up exactly like this:
Backbone.history.stop(); //stop the router
spyOn(Router.prototype, 'index'); //spy on our routes, and they won't get called
spyOn(Router.prototype, 'route2');
router = new App.Router(); // Set up the spies _before_ creating the router
Backbone.history.start();
});
it('empty route routes to index', function(){
Backbone.history.navigate('', trigger);
expect(router.index).toHaveBeenCalled();
});
it('/ routes to index', function(){
router.navigate('/', trigger);
expect(router.index).toHaveBeenCalled();
});
it('/item routes to item with id', function(){
router.navigate('/item/someId', trigger);
expect(router.item).toHaveBeenCalledWith('someId');
});
});
这就是我最终使用自己的东西。我通过扩展它并用空白方法覆盖方法来制作路由器的模拟版本,以防止它在被调用时调用任何进一步的逻辑:
describe("routers/main", function() {
beforeEach(function() {
// Create a mock version of our router by extending it and only overriding
// the methods
var mockRouter = App.Routers["Main"].extend({
index: function() {},
login: function() {},
logoff: function() {}
});
// Set up a spy and invoke the router
this.routeSpy = sinon.spy();
this.router = new mockRouter;
// Prevent history.start from throwing error
try {
Backbone.history.start({silent:true, pushState:true});
} catch(e) {
}
// Reset URL
this.router.navigate("tests/SpecRunner.html");
});
afterEach(function(){
// Reset URL
this.router.navigate("tests/SpecRunner.html");
});
it('Has the right amount of routes', function() {
expect(_.size(this.router.routes)).toEqual(4);
});
it('/ -route exists and points to the right method', function () {
expect(this.router.routes['']).toEqual('index');
});
it("Can navigate to /", function() {
this.router.bind("route:index", this.routeSpy);
this.router.navigate("", true);
expect(this.routeSpy.calledOnce).toBeTruthy();
expect(this.routeSpy.calledWith()).toBeTruthy();
});
});
请注意,sinon.js
上面用于创建间谍,以及underscore.js
提供size
功能。
您必须模拟 Backbone.Router.route,这是内部用于将函数绑定到 Backbone.History 的函数。
那是原始功能:
route : function(route, name, callback) {
Backbone.history || (Backbone.history = new Backbone.History);
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
Backbone.history.route(route, _.bind(function(fragment) {
var args = this._extractParameters(route, fragment);
callback.apply(this, args);
this.trigger.apply(this, ['route:' + name].concat(args));
}, this));
}
你可以像这样,在路由器初始化时简单地调用函数:
Backbone.Router.route = function(route, name, callback) {
callback();
}
您还可以将回调保存在一个对象中,并将路由作为名称并逐步调用相同的步骤:
var map = {}
Backbone.Router.route = function(route, name, callback) {
map[route] = callback();
}
for(i in map){
map[i]();
}
我开始使用ggozad的间谍解决方案,_updateHash
该解决方案对我部分有用。但是,我发现我的测试很混乱,因为哈希从未更新,因此依赖于调用的代码getHash
或getFragment
失败的代码。
我最终得到的是以下辅助函数,它同时监视_updateHash
和getHash
。前者记录更新哈希的请求,后者返回传递给的最后一个哈希_updateHash
。在开始 Backbone 历史记录之前,我在测试中调用了这个辅助函数。
/**
* Prevent Backbone tests from changing the browser's URL.
*
* This function modifies Backbone so that tests can navigate
* without modifying the browser's URL. It works be adding
* stub versions of Backbone's hash functions so that updating
* the hash doesn't change the URL but instead updates a
* local object. The router's callbacks are still invoked
* so that to the test it appears that navigation is behaving
* as expected.
*
* Note: it is important that tests don't update the browser's
* URL because subsequent tests could find themselves in an
* unexpected navigation state.
*/
preventBackboneChangingUrl = function() {
var history = {
currentFragment: ''
};
// Stub out the Backbone router so that the browser doesn't actually navigate
spyOn(Backbone.history, '_updateHash').andCallFake(function (location, fragment, replace) {
history.currentFragment = fragment;
});
// Stub out getHash so that Backbone thinks that the browser has navigated
spyOn(Backbone.history, 'getHash').andCallFake(function () {
return history.currentFragment;
});
};