我正在尝试找出在控制器中单元测试成功和错误回调的最佳方法。只要控制器仅使用默认的$q函数(例如以下示例),我就可以模拟服务方法。但是当控制器响应“success”或“error”Promise时,我遇到了问题。(如果我的术语不正确,请原谅)。
以下是一个示例控制器/服务:
var myControllers = angular.module('myControllers');
myControllers.controller('SimpleController', ['$scope', 'myService',
function ($scope, myService) {
var id = 1;
$scope.loadData = function () {
myService.get(id).then(function (response) {
$scope.data = response.data;
});
};
$scope.loadData2 = function () {
myService.get(id).success(function (response) {
$scope.data = response.data;
}).error(function(response) {
$scope.error = 'ERROR';
});
};
}]);
cocoApp.service('myService', [
'$http', function($http) {
function get(id) {
return $http.get('/api/' + id);
}
}
]);
我有以下测试。
'use strict';
describe('SimpleControllerTests', function () {
var scope;
var controller;
var getResponse = { data: 'this is a mocked response' };
beforeEach(angular.mock.module('myApp'));
beforeEach(angular.mock.inject(function($q, $controller, $rootScope, $routeParams){
scope = $rootScope;
var myServiceMock = {
get: function() {}
};
// setup a promise for the get
var getDeferred = $q.defer();
getDeferred.resolve(getResponse);
spyOn(myServiceMock, 'get').andReturn(getDeferred.promise);
controller = $controller('SimpleController', { $scope: scope, myService: myServiceMock });
}));
it('this tests works', function() {
scope.loadData();
expect(scope.data).toEqual(getResponse.data);
});
it('this doesnt work', function () {
scope.loadData2();
expect(scope.data).toEqual(getResponse.data);
});
});
第一个测试通过了,而第二个测试失败了,报错为“TypeError: Object doesn't support property or method 'success'”。我理解在这种情况下,getDeferred.promise没有一个success函数。那么问题来了,有什么好的方法可以编写这个测试,这样我就可以测试模拟服务的“success”、“error”和“then”条件呢?
我开始觉得应该避免在我的控制器中使用success()和error()。
编辑:
所以经过进一步思考,再次感谢下面详细的答案,我得出了结论:在控制器中处理成功和错误回调是不好的。正如HackedByChinese在下面提到的,success\error是由$http添加的语法糖。实际上,尝试处理success\error会让$http的问题泄漏到我的控制器中,这正是我试图通过将$http调用包装在服务中避免的。我要采取的方法是修改控制器,不再使用success\error。
myControllers.controller('SimpleController', ['$scope', 'myService',
function ($scope, myService) {
var id = 1;
$scope.loadData = function () {
myService.get(id).then(function (response) {
$scope.data = response.data;
}, function (response) {
$scope.error = 'ERROR';
});
};
}]);
这样我可以通过在延迟对象上调用 resolve() 和 reject() 来测试错误和成功条件:
'use strict';
describe('SimpleControllerTests', function () {
var scope;
var controller;
var getResponse = { data: 'this is a mocked response' };
var getDeferred;
var myServiceMock;
//mock Application to allow us to inject our own dependencies
beforeEach(angular.mock.module('myApp'));
//mock the controller for the same reason and include $rootScope and $controller
beforeEach(angular.mock.inject(function($q, $controller, $rootScope, $routeParams) {
scope = $rootScope;
myServiceMock = {
get: function() {}
};
// setup a promise for the get
getDeferred = $q.defer();
spyOn(myServiceMock, 'get').andReturn(getDeferred.promise);
controller = $controller('SimpleController', { $scope: scope, myService: myServiceMock });
}));
it('should set some data on the scope when successful', function () {
getDeferred.resolve(getResponse);
scope.loadData();
scope.$apply();
expect(myServiceMock.get).toHaveBeenCalled();
expect(scope.data).toEqual(getResponse.data);
});
it('should do something else when unsuccessful', function () {
getDeferred.reject(getResponse);
scope.loadData();
scope.$apply();
expect(myServiceMock.get).toHaveBeenCalled();
expect(scope.error).toEqual('ERROR');
});
});
$httpBackend
来设置对 http 调用的期望并指定它们应该如何响应,然后您可以使用flush
同步完成测试。为什么不走这条路呢?请参见:https://docs.angularjs.org/api/ngMock/service/$httpBackend - moribvndvsand.returnValue
,而不是andReturn
。 - a7omiton