Test a controller with success() and error ()(使用 success() 和 error() 测试控制器)
问题描述
我正在尝试找出在控制器中单元测试成功和错误回调的最佳方法.我可以模拟出服务方法,只要控制器只使用默认的 $q 函数,例如then"(参见下面的示例).当控制器响应成功"或错误"承诺时,我遇到了问题.(对不起,如果我的术语不正确).
I'm trying to work out the best way to unit test success and error callbacks in controllers. I am able to mock out service methods, as long as the controller only uses the default $q functions such as 'then' (see the example below). I'm having an issue when the controller responds to a 'success' or 'error' promise. (Sorry if my terminology is not correct).
这是一个示例控制器服务
Here is an example controller service
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 does not support property or method 'success'".在这种情况下,我得到了 getDeferred.promise没有成功功能.好的,这是一个问题,编写此测试的好方法是什么,以便我可以测试成功"、错误"和模拟服务的那么"条件?
The first test passes and the second fails with the error "TypeError: Object doesn't support property or method 'success'". I get that in this instance that getDeferred.promise does not have a success function. Okay here is the question, what is a nice way to write this test so that I can test the 'success', 'error' & 'then' conditions of a mocked service ?
我开始认为我应该避免在我的控制器中使用 success() 和 error()...
I'm starting to think that I should avoid the use of success() and error() in my controllers...
编辑
所以在考虑了更多之后,并且感谢下面的详细答案,我得出结论,在控制器中处理成功和错误回调是不好的. 正如 HackedByChinese 提到的在successerror 下面是$http 添加的语法糖.因此,实际上,通过尝试处理成功错误,我让 $http 问题泄漏到我的控制器中,这正是我试图通过将 $http 调用包装在服务中来避免的.我要采取的方法是把控制器改成不使用successerror:
So after thinking about this some more, and thanks to the detailed answer below, I've come to the conclusion that the handling the success and error callbacks in the controller is bad. As HackedByChinese mentions below successerror is syntactic sugar that is added by $http. So, in actual fact, by trying to handle success error I am letting $http concerns leak into my controller, which is exactly what I was trying to avoid by wrapping the $http calls in a service. The approach I'm going to take is to change the controller not to use 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()来测试错误成功条件:
This way I can test the error success conditions by calling resolve() and reject() on the deferred object:
'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');
});
});
推荐答案
正如有人在删除的答案中提到的,success
和 error
是 $http
所以当你创建自己的 Promise 时它们不存在.你有两个选择:
As someone had mentioned in a deleted answer, success
and error
are syntactic sugar added by $http
so they aren't there when you create your own promise. You have two options:
这个想法是让您的 myService
在不知道它正在被测试的情况下像往常一样运行.$httpBackend
将让您设置期望和响应,并刷新它们,以便您可以同步完成测试.$http
不会更明智,它返回的承诺看起来和功能就像一个真实的承诺.如果您有对 HTTP 期望很少的简单测试,则此选项很好.
The idea is to let your myService
act like it normally would without knowing it's being tested. $httpBackend
will let you set up expectations and responses, and flush them so you can complete your tests synchronously. $http
won't be any wiser and the promise it returns will look and function like a real one. This option is good if you have simple tests with few HTTP expectations.
'use strict';
describe('SimpleControllerTests', function () {
var scope;
var expectedResponse = { name: 'this is a mocked response' };
var $httpBackend, $controller;
beforeEach(module('myApp'));
beforeEach(inject(function(_$rootScope_, _$controller_, _$httpBackend_){
// the underscores are a convention ng understands, just helps us differentiate parameters from variables
$controller = _$controller_;
$httpBackend = _$httpBackend_;
scope = _$rootScope_;
}));
// makes sure all expected requests are made by the time the test ends
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('should load data successfully', function() {
beforeEach(function() {
$httpBackend.expectGET('/api/1').response(expectedResponse);
$controller('SimpleController', { $scope: scope });
// causes the http requests which will be issued by myService to be completed synchronously, and thus will process the fake response we defined above with the expectGET
$httpBackend.flush();
});
it('using loadData()', function() {
scope.loadData();
expect(scope.data).toEqual(expectedResponse);
});
it('using loadData2()', function () {
scope.loadData2();
expect(scope.data).toEqual(expectedResponse);
});
});
describe('should fail to load data', function() {
beforeEach(function() {
$httpBackend.expectGET('/api/1').response(500); // return 500 - Server Error
$controller('SimpleController', { $scope: scope });
$httpBackend.flush();
});
it('using loadData()', function() {
scope.loadData();
expect(scope.error).toEqual('ERROR');
});
it('using loadData2()', function () {
scope.loadData2();
expect(scope.error).toEqual('ERROR');
});
});
});
2 - 返回一个完全模拟的承诺
如果您正在测试的事物具有复杂的依赖关系并且所有设置都令人头疼,那么您可能仍想像您尝试的那样模拟服务和调用本身.不同之处在于您需要完全模拟 promise.这样做的缺点可能是创建所有可能的模拟 Promise,但是您可以通过创建自己的函数来创建这些对象来简化它.
2 - Return a fully-mocked promise
If the thing you're testing has complicated dependencies and all the set-up is a headache, you may still want to mock the services and the calls themselves as you have attempted. The difference is that you'll want to fully mock promise. The downside of this can be creating all the possible mock promises, however you could make that easier by creating your own function for creating these objects.
之所以如此,是因为我们假装它通过立即调用 success
、error
或 then
提供的处理程序来解决,导致同步完成.
The reason this works is because we pretend that it resolves by invoking the handlers provided by success
, error
, or then
immediately, causing it to complete synchronously.
'use strict';
describe('SimpleControllerTests', function () {
var scope;
var expectedResponse = { name: 'this is a mocked response' };
var $controller, _mockMyService, _mockPromise = null;
beforeEach(module('myApp'));
beforeEach(inject(function(_$rootScope_, _$controller_){
$controller = _$controller_;
scope = _$rootScope_;
_mockMyService = {
get: function() {
return _mockPromise;
}
};
}));
describe('should load data successfully', function() {
beforeEach(function() {
_mockPromise = {
then: function(successFn) {
successFn(expectedResponse);
},
success: function(fn) {
fn(expectedResponse);
}
};
$controller('SimpleController', { $scope: scope, myService: _mockMyService });
});
it('using loadData()', function() {
scope.loadData();
expect(scope.data).toEqual(expectedResponse);
});
it('using loadData2()', function () {
scope.loadData2();
expect(scope.data).toEqual(expectedResponse);
});
});
describe('should fail to load data', function() {
beforeEach(function() {
_mockPromise = {
then: function(successFn, errorFn) {
errorFn();
},
error: function(fn) {
fn();
}
};
$controller('SimpleController', { $scope: scope, myService: _mockMyService });
});
it('using loadData()', function() {
scope.loadData();
expect(scope.error).toEqual("ERROR");
});
it('using loadData2()', function () {
scope.loadData2();
expect(scope.error).toEqual("ERROR");
});
});
});
即使在大型应用程序中,我也很少选择选项 2.
I rarely go for option 2, even in big applications.
不管怎样,您的 loadData
和 loadData2
http 处理程序都有错误.他们引用 response.data
但 handlers 将被解析直接响应数据,而不是响应对象(所以它应该是 data
而不是 response.data
).
For what it's worth, your loadData
and loadData2
http handlers have an error. They reference response.data
but the handlers will be called with the parsed response data directly, not the response object (so it should be data
instead of response.data
).
这篇关于使用 success() 和 error() 测试控制器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!
本文标题为:使用 success() 和 error() 测试控制器
- 为什么我的页面无法在 Github 上加载? 2022-01-01
- 如何向 ipc 渲染器发送添加回调 2022-01-01
- 为什么悬停在委托事件处理程序中不起作用? 2022-01-01
- 使用 iframe URL 的 jQuery UI 对话框 2022-01-01
- 在不使用循环的情况下查找数字数组中的一项 2022-01-01
- 从原点悬停时触发 translateY() 2022-01-01
- 我不能使用 json 使用 react 向我的 web api 发出 Post 请求 2022-01-01
- 如何调试 CSS/Javascript 悬停问题 2022-01-01
- 如何显示带有换行符的文本标签? 2022-01-01
- 是否可以将标志传递给 Gulp 以使其以不同的方式 2022-01-01