在测试服务中使用模拟服务承诺解决
promise resolution with a mocked service within the tested service
我的问题:在 Karma 中,我在测试实际服务时模拟注入的服务。模拟服务获取一些数据,并发回承诺。我不知道我做错了什么。
我知道实际的 "Login" 机制存在很多问题,但我用它来说明这个 Karma 问题。我不打算将其用作生产代码。 (但是,欢迎任何更好地说明问题的建议!)
首先,我把它写在一个通用的 .js 文件中,然后说 "node testcode.js"
testcode.js:
function Login(name,password,callback){
setTimeout(function(){
var response;
var promise = getByUserName();
promise.then(successCb);
function successCb(userObj){
if ( userObj != null && userObj.password === password ) {
response = { success : true };
} else {
response = { success: false, message: 'Username or password is incorrect' };
}
callback(response);
};
},200);
}
function getByUserName(){
return Promise.resolve(user);
}
var user = {
username : 'test',
id : 'testId',
password : 'test'
};
var test = undefined;
Login(user.username,user.password,testCb);
function testCb(response){
test = response;
console.log("Final: " + JSON.stringify(test));
}
这给了我预期的结果:
Final: {"success":true}
现在,我尝试在 Karma 中重复这个...
测试服务:
(function(){
"use strict";
angular.module('TestModule').service('TestService',TestService);
TestService.$inject = ['$http','$cookieStore','$rootScope','$timeout','RealService'];
})();
function TestService($http,$cookieStore,$rootScope,$timeout,RealService){
var service = {};
service.Login = Login;
/* more stuff */
return service;
function Login(username,password,callback){
$timeout(function () {
var response;
var promise = UserService.GetByUsername(username);
promise.then(successCb);
function successCb(user){
if (user !== null && user.password === password) {
response = { success: true };
} else {
response = { success: false, message: 'Username or password is incorrect' };
}
callback(response);
};
}, 1000);
}
}
我的业力茉莉测试:
describe('TestModule',function(){
beforeEach(module('TestModule'));
describe('TestService',function(){
var service,$rootScope,
user = {
username : 'test',
id : 'testId',
password : 'test'
};
function MockService() {
return {
GetByUsername : function() {
return Promise.resolve(user);
}
};
};
beforeEach(function(){
module(function($provide){
$provide.service('RealService',MockService);
});
inject(['$rootScope','TestService',
function($rs,ts){
$rootScope = $rs;
service = ts;
}]);
});
/**
* ERROR: To the best of my knowledge, this should not pass
*/
it('should Login',function(){
expect(service).toBeDefined();
var answer = {"success":true};
service.Login(user.username,user.password,testCb);
//$rootScope.$apply(); <-- DID NOTHING -->
//$rootScope.$digest(); <-- DID NOTHING -->
function testCb(response){
console.log("I'm never called");
expect(response.success).toBe(false);
expect(true).toBe(false);
};
});
});
});
为什么承诺没有得到解决?我曾尝试根据我在 SO 上阅读过的类似问题弄乱 $rootScope.$digest(),但似乎没有任何东西可以调用 testCb。
如果我可以给 'estus' 这个答案的功劳,请告诉我。
神奇之处在于 $timeout angular 模拟服务:
https://docs.angularjs.org/api/ngMock/service/$超时
和 $q 服务:
https://docs.angularjs.org/api/ng/service/$q
我更新了两件事。首先,我的 MockUserService 使用 $q.defer 而不是本机承诺。正如 'estus' 所述,$q 承诺是同步的,这在 karma-jasmine 测试中很重要。
function MockUserService(){
return {
GetByUsername : function(unusedVariable){
//The infamous Deferred antipattern:
//var defer = $q.defer();
//defer.resolve(user);
//return defer.promise;
return $q.resolve(user);
}
};
};
我进行的下一次更新是 $timeout 服务:
it('should Login',function(){
expect(service).toBeDefined();
service.Login(user.username,user.password,testCb);
$timeout.flush(); <-- NEW, forces the $timeout in TestService to execute -->
$timeout.verifyNoPendingTasks(); <-- NEW -->
function testCb(response) {
expect(response.success).toBe(true);
};
});
最后,因为我在测试中使用了 $q 和 $timeout,所以我不得不更新 beforeEach 中的注入方法:
inject([
'$q','$rootScope','$timeout','TestService',
function(_$q_,$rs,$to,ts) {
$q = _$q_;
$rootScope = $rs;
$timeout = $to;
service = ts;
}
]);
我的问题:在 Karma 中,我在测试实际服务时模拟注入的服务。模拟服务获取一些数据,并发回承诺。我不知道我做错了什么。
我知道实际的 "Login" 机制存在很多问题,但我用它来说明这个 Karma 问题。我不打算将其用作生产代码。 (但是,欢迎任何更好地说明问题的建议!)
首先,我把它写在一个通用的 .js 文件中,然后说 "node testcode.js"
testcode.js:
function Login(name,password,callback){
setTimeout(function(){
var response;
var promise = getByUserName();
promise.then(successCb);
function successCb(userObj){
if ( userObj != null && userObj.password === password ) {
response = { success : true };
} else {
response = { success: false, message: 'Username or password is incorrect' };
}
callback(response);
};
},200);
}
function getByUserName(){
return Promise.resolve(user);
}
var user = {
username : 'test',
id : 'testId',
password : 'test'
};
var test = undefined;
Login(user.username,user.password,testCb);
function testCb(response){
test = response;
console.log("Final: " + JSON.stringify(test));
}
这给了我预期的结果:
Final: {"success":true}
现在,我尝试在 Karma 中重复这个...
测试服务:
(function(){
"use strict";
angular.module('TestModule').service('TestService',TestService);
TestService.$inject = ['$http','$cookieStore','$rootScope','$timeout','RealService'];
})();
function TestService($http,$cookieStore,$rootScope,$timeout,RealService){
var service = {};
service.Login = Login;
/* more stuff */
return service;
function Login(username,password,callback){
$timeout(function () {
var response;
var promise = UserService.GetByUsername(username);
promise.then(successCb);
function successCb(user){
if (user !== null && user.password === password) {
response = { success: true };
} else {
response = { success: false, message: 'Username or password is incorrect' };
}
callback(response);
};
}, 1000);
}
}
我的业力茉莉测试:
describe('TestModule',function(){
beforeEach(module('TestModule'));
describe('TestService',function(){
var service,$rootScope,
user = {
username : 'test',
id : 'testId',
password : 'test'
};
function MockService() {
return {
GetByUsername : function() {
return Promise.resolve(user);
}
};
};
beforeEach(function(){
module(function($provide){
$provide.service('RealService',MockService);
});
inject(['$rootScope','TestService',
function($rs,ts){
$rootScope = $rs;
service = ts;
}]);
});
/**
* ERROR: To the best of my knowledge, this should not pass
*/
it('should Login',function(){
expect(service).toBeDefined();
var answer = {"success":true};
service.Login(user.username,user.password,testCb);
//$rootScope.$apply(); <-- DID NOTHING -->
//$rootScope.$digest(); <-- DID NOTHING -->
function testCb(response){
console.log("I'm never called");
expect(response.success).toBe(false);
expect(true).toBe(false);
};
});
});
});
为什么承诺没有得到解决?我曾尝试根据我在 SO 上阅读过的类似问题弄乱 $rootScope.$digest(),但似乎没有任何东西可以调用 testCb。
如果我可以给 'estus' 这个答案的功劳,请告诉我。
神奇之处在于 $timeout angular 模拟服务: https://docs.angularjs.org/api/ngMock/service/$超时
和 $q 服务: https://docs.angularjs.org/api/ng/service/$q
我更新了两件事。首先,我的 MockUserService 使用 $q.defer 而不是本机承诺。正如 'estus' 所述,$q 承诺是同步的,这在 karma-jasmine 测试中很重要。
function MockUserService(){
return {
GetByUsername : function(unusedVariable){
//The infamous Deferred antipattern:
//var defer = $q.defer();
//defer.resolve(user);
//return defer.promise;
return $q.resolve(user);
}
};
};
我进行的下一次更新是 $timeout 服务:
it('should Login',function(){
expect(service).toBeDefined();
service.Login(user.username,user.password,testCb);
$timeout.flush(); <-- NEW, forces the $timeout in TestService to execute -->
$timeout.verifyNoPendingTasks(); <-- NEW -->
function testCb(response) {
expect(response.success).toBe(true);
};
});
最后,因为我在测试中使用了 $q 和 $timeout,所以我不得不更新 beforeEach 中的注入方法:
inject([
'$q','$rootScope','$timeout','TestService',
function(_$q_,$rs,$to,ts) {
$q = _$q_;
$rootScope = $rs;
$timeout = $to;
service = ts;
}
]);