使用 Angular/Jasmine 测试两个已解决的承诺
Testing two resolved promises with Angular/Jasmine
我有一个名为 myHttp
的服务,它 returns 一个承诺,还有一个使用不同参数调用 myHttp
两次的控制器。
为了测试控制器,我尝试使用 Jasmine spyOn 模拟 myHttp
,如下所示:
beforeEach(inject(function($controller, _$rootScope_, _$q_, myHttp) {
$scope = _$rootScope_.$new();
$q = _$q_;
deferred = $q.defer();
deferred2 = $q.defer();
spyOn(myHttp, 'call').and.callFake(fake(1)).and.returnValue(deferred.promise);
spyOn(myHttp, 'call').and.callFake(fake(2)).and.returnValue(deferred2.promise);
其中 fake
是一个函数,用于检索要在 myHttp
调用中使用的参数。
问题是我无法声明两次相同的模拟函数。我从 Jasmine 收到以下错误:
Error: call has already been spied upon
如何解决这个测试?这是 PLUNK
Javascript:
angular.module("mymodule", [])
.service('myHttp', function($http,$q){
this.call = function(obj) {
var defer = $q.defer();
$http({url:obj.url, data:obj.data})
.then(function (response) {
defer.resolve(response);
});
return defer.promise;
};
})
.controller('ctl', function($scope,myHttp) {
$scope.read = function (id){
var data = {};
data.id = id;
myHttp.call({url:'/getStudent', data:data})
.then(function(response) {
$scope.id = response.id;
$scope.name = response.nm;
$scope.classId = response.clsid;
var data2 = {};
data2.id = $scope.classId;
myHttp.call({url:'/getClass', data:data2})
.then(function(response) {
$scope.className = response.nm;
});
});
};
});
describe('Testing a Controller that uses a Promise', function () {
var $scope;
var $q;
var deferred;
var $timeout;
beforeEach(module('mymodule'));
beforeEach(inject(function($controller, _$rootScope_, _$q_, myHttp) {
$scope = _$rootScope_.$new();
$q = _$q_;
deferred = $q.defer();
deferred2 = $q.defer();
spyOn(myHttp, 'call').and.callFake(fake(1)).and.returnValue(deferred.promise);
spyOn(myHttp, 'call').and.callFake(fake(2)).and.returnValue(deferred2.promise);
$controller('ctl', {
$scope: $scope,
myHttp: myHttp
});
$scope.read(1)
}));
function fake (option) {
if (option==1)
return {url:'/getStudent', data: {id: 1}};
else
return {url:'/getClass', data: {id: 10}};
}
it('should resolve two promises', function () {
var student = {
id: 1,
nm: "John",
clsid: 10
};
var clazz = {
id: 10,
nm: "Math"
};
deferred.resolve(student);
$scope.$apply();
deferred2.resolve(clazz);
$scope.$apply();
expect($scope.id).toBe(student.id);
expect($scope.name).toBe(student.nm);
expect($scope.classId).toBe(student.clsid);
expect($scope.className).toBe(clazz.nm);
});
});
最普遍的答案是您不必在 top-level beforeEach 时创建间谍。您可以使用更接近单个规范的 beforeEach,甚至可以在 it 语句中创建间谍。您还可以检查您的假货中的某些内容,以根据调用假货的参数查看您想要 return 的承诺。
但是,您实际上想要检查什么条件?通常在 promise 被 resolve 之后应该发生一些事情,而你实际上想看看那个。如果您说出您最终的目标是什么,而不是说出您认为什么是正确的解决方案并询问我们如何实现,那么给您具体的反馈会更容易。
只需监视 myHttp.call
一次并更改您的假函数调用以接收请求参数和 return 基于请求的对象。
var student = {
id: 1,
nm: "John",
clsid: 10
};
var clazz = {
id: 10,
nm: "Math"
};
spyOn(myHttp, 'call').and.callFake(function(obj) {
if (obj.url == '/getStudent') {
return $q.when(student);
} else if (obj.url = '/getClass') {
return $q.when(clazz);
}
return $q.reject('Mock not supported');
});
有关完整的工作示例,请参阅 plunker。
另外,您的 http 调用将减少到
.service('myHttp', function($http,$q){
this.call = function(obj) {
return $http({url:obj.url, data:obj.data});
};
})
因为 $http
已经 return 是一个承诺。
此外,您还可以使用 angular $httpBackend
to mock your http calls instead of using the jasmine spyon. See this plunker,它使用 $httpBackend
并稍微更改为 myHttp.call
。
我有一个名为 myHttp
的服务,它 returns 一个承诺,还有一个使用不同参数调用 myHttp
两次的控制器。
为了测试控制器,我尝试使用 Jasmine spyOn 模拟 myHttp
,如下所示:
beforeEach(inject(function($controller, _$rootScope_, _$q_, myHttp) {
$scope = _$rootScope_.$new();
$q = _$q_;
deferred = $q.defer();
deferred2 = $q.defer();
spyOn(myHttp, 'call').and.callFake(fake(1)).and.returnValue(deferred.promise);
spyOn(myHttp, 'call').and.callFake(fake(2)).and.returnValue(deferred2.promise);
其中 fake
是一个函数,用于检索要在 myHttp
调用中使用的参数。
问题是我无法声明两次相同的模拟函数。我从 Jasmine 收到以下错误:
Error: call has already been spied upon
如何解决这个测试?这是 PLUNK
Javascript:
angular.module("mymodule", [])
.service('myHttp', function($http,$q){
this.call = function(obj) {
var defer = $q.defer();
$http({url:obj.url, data:obj.data})
.then(function (response) {
defer.resolve(response);
});
return defer.promise;
};
})
.controller('ctl', function($scope,myHttp) {
$scope.read = function (id){
var data = {};
data.id = id;
myHttp.call({url:'/getStudent', data:data})
.then(function(response) {
$scope.id = response.id;
$scope.name = response.nm;
$scope.classId = response.clsid;
var data2 = {};
data2.id = $scope.classId;
myHttp.call({url:'/getClass', data:data2})
.then(function(response) {
$scope.className = response.nm;
});
});
};
});
describe('Testing a Controller that uses a Promise', function () {
var $scope;
var $q;
var deferred;
var $timeout;
beforeEach(module('mymodule'));
beforeEach(inject(function($controller, _$rootScope_, _$q_, myHttp) {
$scope = _$rootScope_.$new();
$q = _$q_;
deferred = $q.defer();
deferred2 = $q.defer();
spyOn(myHttp, 'call').and.callFake(fake(1)).and.returnValue(deferred.promise);
spyOn(myHttp, 'call').and.callFake(fake(2)).and.returnValue(deferred2.promise);
$controller('ctl', {
$scope: $scope,
myHttp: myHttp
});
$scope.read(1)
}));
function fake (option) {
if (option==1)
return {url:'/getStudent', data: {id: 1}};
else
return {url:'/getClass', data: {id: 10}};
}
it('should resolve two promises', function () {
var student = {
id: 1,
nm: "John",
clsid: 10
};
var clazz = {
id: 10,
nm: "Math"
};
deferred.resolve(student);
$scope.$apply();
deferred2.resolve(clazz);
$scope.$apply();
expect($scope.id).toBe(student.id);
expect($scope.name).toBe(student.nm);
expect($scope.classId).toBe(student.clsid);
expect($scope.className).toBe(clazz.nm);
});
});
最普遍的答案是您不必在 top-level beforeEach 时创建间谍。您可以使用更接近单个规范的 beforeEach,甚至可以在 it 语句中创建间谍。您还可以检查您的假货中的某些内容,以根据调用假货的参数查看您想要 return 的承诺。
但是,您实际上想要检查什么条件?通常在 promise 被 resolve 之后应该发生一些事情,而你实际上想看看那个。如果您说出您最终的目标是什么,而不是说出您认为什么是正确的解决方案并询问我们如何实现,那么给您具体的反馈会更容易。
只需监视 myHttp.call
一次并更改您的假函数调用以接收请求参数和 return 基于请求的对象。
var student = {
id: 1,
nm: "John",
clsid: 10
};
var clazz = {
id: 10,
nm: "Math"
};
spyOn(myHttp, 'call').and.callFake(function(obj) {
if (obj.url == '/getStudent') {
return $q.when(student);
} else if (obj.url = '/getClass') {
return $q.when(clazz);
}
return $q.reject('Mock not supported');
});
有关完整的工作示例,请参阅 plunker。
另外,您的 http 调用将减少到
.service('myHttp', function($http,$q){
this.call = function(obj) {
return $http({url:obj.url, data:obj.data});
};
})
因为 $http
已经 return 是一个承诺。
此外,您还可以使用 angular $httpBackend
to mock your http calls instead of using the jasmine spyon. See this plunker,它使用 $httpBackend
并稍微更改为 myHttp.call
。