我正在使用O'Reilly的AngularJS书籍来学习AngularJS。然后在书中创建一个服务,它的任务是加载$resource,如下所示:
services.factory('Recipe', ['$resource', function($resource) {
return $resource('/recipes/:id', {id: '@id'});
}]);
services.factory('RecipeLoader', ['Recipe', '$route', '$q', function(Recipe, $route, $q) {
return function() {
var delay = $q.defer();
Recipe.get({id: $route.current.params.recipeId}, function(recipe) {
delay.resolve(recipe);
}, function() {
delay.reject('Unable to fetch recipe ' + $route.current.params.recipeId);
});
return delay.promise;
};
});
然而,在书中忽略了测试这个服务,我正在努力找出如何做到这一点。我已经创建了一个使用模拟$httpBackend的测试,但我遇到的问题是当测试运行$route时。当前未定义
我已经尝试注入$route到我的测试和设置$route.current.params。Id = 1;但这似乎行不通。有人对如何在$route.current.params中设置测试值有任何建议吗?
多谢! !
解决方案我找到了一个更好的解决方案,我让工厂把id作为参数,像这样:
services.factory('RecipeLoader', ['Recipe', '$route', '$q', function(Recipe, $route, $q) {
return function(recipeId) {
var delay = $q.defer();
Recipe.get({id: recipeId}, function(recipe) {
delay.resolve(recipe);
}, function() {
delay.reject('Unable to fetch recipe ' + $route.current.params.recipeId);
});
return delay.promise;
};
});
然后修改$routeProvider中的resolve参数,传入ID。就像
.when('/recipe/view/:id', {
templateUrl: 'views/recipe/view.html',
controller: 'RecipeViewCtrl',
resolve: {
renewal: function (RecipeLoader, $route) {
return RecipeLoader($route.current.params.id);
}
}
})
我觉得这是更好的设计,因为控制器更具体地说明了它需要什么输入。
您是正确的,您偶然发现的解决方案是创建此服务并对其进行测试的更好方法。对于服务,它应该独立于魔法,就像路由或状态一样,并且应该传入它需要的东西。
书中的代码更多地展示了如何同时使用服务和解析,而不是编写服务的正确方法。
对于服务,只要可能,更倾向于传递参数,而不是从魔法状态读取参数。
也就是说,这是一个有趣的问题,当你需要测试依赖于某些服务的其他服务时,你如何模拟它们。
有两种方法可以在测试中覆盖服务:
-
为单个单元测试或描述块的目的重写它。在使用
beforeEach(module('myModule'))
初始化您自己的模块之后,您可以创建一个内联模块,如下所示:beforeEach(module(function($provide) { $provide.factory('ServiceToOverride', function() { // Return overriden service here }); }));
链接:Plnkr
-
创建一个用于通用mock的模块,例如
angular.module('myApp.mock')
,并在其中定义您的覆盖服务。然后,在beforeEach加载测试中的模块之后,还使用beforeEach(module('myApp.mock'));
加载此模块。
后者对于更密集和逻辑封装的模拟更有用,而前者对于快速、一次性、单次使用的模拟非常有用。