我正在寻找在Ember.js应用程序中对路线进行单元测试的最佳解决方案。我找到了两个解决方案,我希望你能告诉我什么对你最好。 这两个实现可在此处获得:http://jsbin.com/URaKULa/1/edit
//=====================================
// Source :
//=====================================
App = Em.Application.create({
});
App.UserEditRoute = Ember.Route.extend({
model: function () {
// here we tell the route to use its parent model
return this.modelFor('user');
},
activate: function () {
this.controllerFor('user').set('editMode', true);
},
deactivate: function () {
this.controllerFor('user').set('editMode', false);
},
events: {
goBack: function () {
this.transitionTo('user');
}
}
});
// defer readiness and set location's router to none in order to stop main application initialization
App.setupForTesting();
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//=====================================
// Test :
//=====================================
//-----------------
// First solution
//-----------------
(function () {
var originalTemplates;
var container;
var Router;
var router;
function bootTestApplication() {
router = container.lookup('router:main');
Ember.run(TestApp, 'advanceReadiness');
}
function handleURL(path) {
return Ember.run(function () {
return router.handleURL(path).then(function (value) {
ok(true, 'url: `' + path + '` was handled');
return value;
}, function (reason) {
ok(false, 'failed to visit:`' + path + '` reason: `' + reason);
throw reason;
});
});
}
module('First solution for test routes', {
setup: function () {
Em.run(function () {
// We create a fake test Application to isalate route
TestApp = Em.Application.create({
LOG_TRANSITIONS: true,
name: "TestApp",
rootElement: "#qunit-fixture"
});
// We defer initialization and disabled location
TestApp.setupForTesting();
// Save the router
Router = TestApp.Router;
// Save a loading route just in case
TestApp.LoadingRoute = Ember.Route.extend({
});
// Save the global TestContainer to lookup what you want
container = TestApp.__container__;
// We save the current ember templates to not impact the other unit test
// when we will overide a few.
originalTemplates = Ember.$.extend({}, Ember.TEMPLATES);
// Override/mock application test for isolation
Ember.TEMPLATES.application = Ember.Handlebars.compile("{{outlet}}");
// You can here overide other templates if necessary
});
},
teardown: function () {
Ember.run(function () {
// deleting the Test application
TestApp.destroy();
TestApp = null;
// reset old Ember templates
Ember.TEMPLATES = originalTemplates;
});
}
});
test('UserEditRoute', function () {
Router.map(function () {
this.resource('user', function () {
this.route('edit');
});
});
// Save the App.UserEditRoute to test into the isolated context
TestApp.UserEditRoute = App.UserEditRoute;
// we define a fake UserRoute who will return the expected model of the UserEditRoute
var expectedUserModel = Em.Object.create({name: 'Model from user route'});
TestApp.UserRoute = Em.Route.extend({
model: function () {
return expectedUserModel;
}
});
// Mock of UserController and UserEditController classes
TestApp.UserController = Em.Controller.extend({
editMode: false
});
TestApp.UserEditController = Em.Controller;
// We define an empty template
Ember.TEMPLATES['user/edit'] = Ember.Handlebars.compile("");
// Start TestApp application
bootTestApplication();
// retrieve UserController instance
var userCtrl = container.lookup('controller:user');
var userEditCtrl = container.lookup('controller:userEdit');
handleURL('/user/edit');
// assert activate hook behavior and model
ok(userCtrl.get('editMode'));
ok(userEditCtrl.get('content', expectedUserModel));
// reset tested properties
userCtrl.setProperties({
editMode: true,
content: null
});
handleURL('/');
// assert deactivate hook behavior
ok(!userCtrl.get('editMode'));
// here we place other test like event handling, or to test others route's hooks
});
})();
//-----------------
// Second solution
//-----------------
(function(){
module('Second solution to test routes',{
setup:function(){
},
teardown:function(){
}
});
test('UserEditRoute',function(){
var container = new Em.Container();
var expectedUserModel = Em.Object.create({name: 'Model from user route'});
container.register("controller:user", Em.Controller.extend({
editMode: false
}));
container.register("route:user", Em.Route.extend({
currentModel:expectedUserModel
}));
var userEditRoute = App.UserEditRoute.create({
router : {
container:container,
router:{}
},
container:container
});
var userCtrl = container.lookup('controller:user');
equal (userEditRoute.model(), expectedUserModel);
userEditRoute.activate();
equal (userCtrl.get('editMode'), true);
userEditRoute.deactivate();
equal (userCtrl.get('editMode'), false);
});
})();
第一个解决方案基于单元测试的Ember.js团队(更多详细信息,请参见此处https://github.com/emberjs/ember.js/blob/master/packages/ember/tests/routing/basic_test.js)这种方法的优点是您可以测试路由的全局行为而不是测试每个钩子。例如,如果我将“editMode”设置移动到另一个钩子,如“setupController”,则测试继续通过,这是正常的。 此外,通过这种方式,我可以通过在“用户/编辑”模板中添加操作来测试事件处理。
这种方法的最大缺点是真的很无聊,我们测试的代码是实现的四倍......
第二种解决方案更轻巧,更优雅,但灵活性更低。我们使用允许注入依赖之王的容器类,并允许模拟所有连接的类,如UserRoute或UserController。这种方法要求您在Emberjs内部模拟路径使用的所有必要属性和方法(例如路由器的例子)。
那么你最喜欢的是什么?或者你有另一个?