如何测试控制器内服务的响应
How to test response from service within controller
我正在尝试测试在控制器中执行的服务 http 调用的响应:
控制器:
define(['module'], function (module) {
'use strict';
var MyController = function ($scope, MyService) {
var vm = this;
$scope.testScope = 'karma is working!';
MyService.getData().then(function (data) {
$scope.result = data.hour
vm.banner = {
'greeting': data.greeting
}
});
};
module.exports = ['$scope', 'MyService', MyController ];
});
单元测试:
define(['require', 'angular-mocks'], function (require) {
'use strict';
var angular = require('angular');
describe("<- MyController Spec ->", function () {
var controller, scope, myService, serviceResponse;
serviceResponse= {
greeting: 'hello',
hour: '12'
};
beforeEach(angular.mock.module('myApp'));
beforeEach(inject(function (_$controller_, _$rootScope_, _MyService_, $q) {
scope = _$rootScope_.$new();
var deferred = $q.defer();
deferred.resolve(serviceResponse);
myService = _MyService_;
spyOn(myService, 'getData').and.returnValue(deferred.promise);
controller = _$controller_('MyController', {$scope: scope});
scope.$apply();
}));
it('should verify that the controller exists ', function() {
expect(controller).toBeDefined();
});
it('should have testScope scope equaling *karma is working*', function() {
expect(scope.testScope ).toEqual('karma is working!');
});
});
});
我如何测试 http
请求是否执行以及 returns serviceResponse
绑定到 $scope.result
和 vm.banner greeting
我试过:
define(['require', 'angular-mocks'], function (require) {
'use strict';
var angular = require('angular');
describe("<- MyController Spec ->", function () {
var controller, scope, myService, serviceResponse, $httpBackend;
serviceResponse= {
greeting: 'hello',
hour: '12'
};
beforeEach(angular.mock.module('myApp'));
beforeEach(inject(function (_$controller_, _$rootScope_, _MyService_, _$httpBackend_) {
scope = _$rootScope_.$new();
$httpBackend = _$httpBackend_
$httpBackend.expectGET("/my/endpoint/here").respond(serviceResponse);
myService = _MyService_;
spyOn(myService, 'getData').and.callThrough();
controller = _$controller_('MyController', {$scope: scope});
scope.$apply();
}));
it('should call my service and populate scope.result ', function() {
myService.getData();
expect(scope.result ).toEqual(serviceResponse.hour);
});
it('should verify that the controller exists ', function() {
expect(controller).toBeDefined();
});
it('should have testScope scope equaling *karma is working*', function() {
expect(scope.testScope ).toEqual('karma is working!');
});
});
});
出现错误:
[should call my service and populate scope.result ----- Expected undefined to be defined.
我认为问题是你的服务 returns 一个承诺,所以当你这样做时 it
it('should call my service and populate scope.result ', function() {
myService.getData();
expect(scope.result ).toEqual(serviceResponse.hour);
});
您的服务在到达 expect
之前可能尚未解决,因此您必须先等待您的 promise then
并在其中执行 expect。
您可以做的是将承诺分配给 $scope.result
var MyController = function ($scope, MyService) {
var vm = this;
$scope.testScope = 'karma is working!';
$scope.result = MyService.getData().then(function (data) {
$scope.result = data.hour
vm.banner = {
'greeting': data.greeting
}
});
};
然后在你的测试中,你可以做类似
的事情
it('should call my service and populate scope.result ', function() {
//myService.getData(); <- you don't have to call this
scope.result.then(function(){
expect(scope.result).toEqual(serviceResponse.hour);
});
});
您将需要模拟 $httpBackend
并期望某些请求并提供模拟响应数据。这是来自 angular docs
的片段
beforeEach(inject(function($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests
$httpBackend.when('GET', '/auth.py')
.respond({userId: 'userX'}, {'A-Token': 'xxx'});
}));
现在,只要 $http 调用 get 到 /auth.py
,它就会用模拟数据进行响应 {userId: 'userX'}, {'A-Token': 'xxx'}
您不应该在控制器中测试服务的行为。在单独的测试中测试控制器和服务。
控制器测试
在您的控制器中,您不应该关心来自服务的数据来自何处,您应该测试返回的结果是否按预期使用。验证您的控制器在初始化时是否调用了所需的方法,并且 $scope.result
和 vm.banner
的值是否符合您的预期。
类似于:
it('should have called my service and populated scope.result ', function() {
expect(myService.getData).toHaveBeenCalled();
expect(scope.result).toEqual(serviceResponse.hour);
expect(vm.banner).toEqual({greeting:serviceResponse.greeting});
});
服务测试
另一方面,您的服务测试应该知道 $http
调用并且应该验证响应,因此使用来自@Luie Almeda 响应的相同资源,为您的服务编写一个单独的测试,调用执行模拟 $http 调用的方法 getData()
和 returns 所需的结果。
类似于:
it('Should return serviceResponse', function () {
var serviceResponse= {
greeting: 'hello',
hour: '12'
};
var myData,
$httpBackend.whenGET('GET_DATA_URL').respond(200, userviceResponse);
MyService.getData().then(function (response){
myData = response
})
$httpBackend.flush();
expect(myData).toBe(serviceResponse);
});
您需要用 MyService.getData()
调用的正确 url 替换 GET_DATA_URL
。始终尝试将测试保持在与代码相同的模块中。在控制器测试中测试控制器代码,在服务测试中测试服务代码。
我的理解是,在期待结果的同时,您已经创建了一个 Ajax & 在 promise 得到解决时设置数据值。
基本上,当您在控制器实例化上调用 myService.getData()
方法时,它会执行 $http
,这将间接执行 $httpBackend
ajax 并将 return mockData
从中。但是,一旦您拨打电话,您就不会等待呼叫完成,然后您正在呼叫您的 assert
声明,这使您的状态失败。
看下面代码注释解释
it('should call my service and populate scope.result ', function() {
//on controller instantiation only we make $http call
//which will make your current execution state of your code in async state
//and javascript transfer its execution control over next line which are assert statement
//then obiviously you will have undefined value.
//you don't need to call below line here, as below method already called when controller instantiated
//myService.getData();
//below line gets called without waiting for dependent ajax to complete.
expect(scope.result ).toEqual(serviceResponse.hour);
});
所以现在你明白了你需要告诉我们的测试代码,我们需要等待 ajax 调用结束时执行断言语句。因此,您可以使用 $httpBackend.flush()
方法在这种情况下对您有帮助。在将控制传递给下一行之前,它将清除所有 $httpBackend
调用队列清除。
it('should call my service and populate scope.result ', function() {
$httpBackend.flush();// will make sure above ajax code has received response
expect(scope.result ).toEqual(serviceResponse.hour);
});
我正在尝试测试在控制器中执行的服务 http 调用的响应:
控制器:
define(['module'], function (module) {
'use strict';
var MyController = function ($scope, MyService) {
var vm = this;
$scope.testScope = 'karma is working!';
MyService.getData().then(function (data) {
$scope.result = data.hour
vm.banner = {
'greeting': data.greeting
}
});
};
module.exports = ['$scope', 'MyService', MyController ];
});
单元测试:
define(['require', 'angular-mocks'], function (require) {
'use strict';
var angular = require('angular');
describe("<- MyController Spec ->", function () {
var controller, scope, myService, serviceResponse;
serviceResponse= {
greeting: 'hello',
hour: '12'
};
beforeEach(angular.mock.module('myApp'));
beforeEach(inject(function (_$controller_, _$rootScope_, _MyService_, $q) {
scope = _$rootScope_.$new();
var deferred = $q.defer();
deferred.resolve(serviceResponse);
myService = _MyService_;
spyOn(myService, 'getData').and.returnValue(deferred.promise);
controller = _$controller_('MyController', {$scope: scope});
scope.$apply();
}));
it('should verify that the controller exists ', function() {
expect(controller).toBeDefined();
});
it('should have testScope scope equaling *karma is working*', function() {
expect(scope.testScope ).toEqual('karma is working!');
});
});
});
我如何测试 http
请求是否执行以及 returns serviceResponse
绑定到 $scope.result
和 vm.banner greeting
我试过:
define(['require', 'angular-mocks'], function (require) {
'use strict';
var angular = require('angular');
describe("<- MyController Spec ->", function () {
var controller, scope, myService, serviceResponse, $httpBackend;
serviceResponse= {
greeting: 'hello',
hour: '12'
};
beforeEach(angular.mock.module('myApp'));
beforeEach(inject(function (_$controller_, _$rootScope_, _MyService_, _$httpBackend_) {
scope = _$rootScope_.$new();
$httpBackend = _$httpBackend_
$httpBackend.expectGET("/my/endpoint/here").respond(serviceResponse);
myService = _MyService_;
spyOn(myService, 'getData').and.callThrough();
controller = _$controller_('MyController', {$scope: scope});
scope.$apply();
}));
it('should call my service and populate scope.result ', function() {
myService.getData();
expect(scope.result ).toEqual(serviceResponse.hour);
});
it('should verify that the controller exists ', function() {
expect(controller).toBeDefined();
});
it('should have testScope scope equaling *karma is working*', function() {
expect(scope.testScope ).toEqual('karma is working!');
});
});
});
出现错误:
[should call my service and populate scope.result ----- Expected undefined to be defined.
我认为问题是你的服务 returns 一个承诺,所以当你这样做时 it
it('should call my service and populate scope.result ', function() {
myService.getData();
expect(scope.result ).toEqual(serviceResponse.hour);
});
您的服务在到达 expect
之前可能尚未解决,因此您必须先等待您的 promise then
并在其中执行 expect。
您可以做的是将承诺分配给 $scope.result
var MyController = function ($scope, MyService) {
var vm = this;
$scope.testScope = 'karma is working!';
$scope.result = MyService.getData().then(function (data) {
$scope.result = data.hour
vm.banner = {
'greeting': data.greeting
}
});
};
然后在你的测试中,你可以做类似
的事情it('should call my service and populate scope.result ', function() {
//myService.getData(); <- you don't have to call this
scope.result.then(function(){
expect(scope.result).toEqual(serviceResponse.hour);
});
});
您将需要模拟 $httpBackend
并期望某些请求并提供模拟响应数据。这是来自 angular docs
beforeEach(inject(function($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests
$httpBackend.when('GET', '/auth.py')
.respond({userId: 'userX'}, {'A-Token': 'xxx'});
}));
现在,只要 $http 调用 get 到 /auth.py
,它就会用模拟数据进行响应 {userId: 'userX'}, {'A-Token': 'xxx'}
您不应该在控制器中测试服务的行为。在单独的测试中测试控制器和服务。
控制器测试
在您的控制器中,您不应该关心来自服务的数据来自何处,您应该测试返回的结果是否按预期使用。验证您的控制器在初始化时是否调用了所需的方法,并且 $scope.result
和 vm.banner
的值是否符合您的预期。
类似于:
it('should have called my service and populated scope.result ', function() {
expect(myService.getData).toHaveBeenCalled();
expect(scope.result).toEqual(serviceResponse.hour);
expect(vm.banner).toEqual({greeting:serviceResponse.greeting});
});
服务测试
另一方面,您的服务测试应该知道 $http
调用并且应该验证响应,因此使用来自@Luie Almeda 响应的相同资源,为您的服务编写一个单独的测试,调用执行模拟 $http 调用的方法 getData()
和 returns 所需的结果。
类似于:
it('Should return serviceResponse', function () {
var serviceResponse= {
greeting: 'hello',
hour: '12'
};
var myData,
$httpBackend.whenGET('GET_DATA_URL').respond(200, userviceResponse);
MyService.getData().then(function (response){
myData = response
})
$httpBackend.flush();
expect(myData).toBe(serviceResponse);
});
您需要用 MyService.getData()
调用的正确 url 替换 GET_DATA_URL
。始终尝试将测试保持在与代码相同的模块中。在控制器测试中测试控制器代码,在服务测试中测试服务代码。
我的理解是,在期待结果的同时,您已经创建了一个 Ajax & 在 promise 得到解决时设置数据值。
基本上,当您在控制器实例化上调用 myService.getData()
方法时,它会执行 $http
,这将间接执行 $httpBackend
ajax 并将 return mockData
从中。但是,一旦您拨打电话,您就不会等待呼叫完成,然后您正在呼叫您的 assert
声明,这使您的状态失败。
看下面代码注释解释
it('should call my service and populate scope.result ', function() {
//on controller instantiation only we make $http call
//which will make your current execution state of your code in async state
//and javascript transfer its execution control over next line which are assert statement
//then obiviously you will have undefined value.
//you don't need to call below line here, as below method already called when controller instantiated
//myService.getData();
//below line gets called without waiting for dependent ajax to complete.
expect(scope.result ).toEqual(serviceResponse.hour);
});
所以现在你明白了你需要告诉我们的测试代码,我们需要等待 ajax 调用结束时执行断言语句。因此,您可以使用 $httpBackend.flush()
方法在这种情况下对您有帮助。在将控制传递给下一行之前,它将清除所有 $httpBackend
调用队列清除。
it('should call my service and populate scope.result ', function() {
$httpBackend.flush();// will make sure above ajax code has received response
expect(scope.result ).toEqual(serviceResponse.hour);
});