我目前正在尝试使用SinonJS对我的角度服务进行单元测试,但一直遇到问题,并希望有人能够解释为什么会发生这种情况。我重建了我目前的项目来说明手头的问题。
我还提供了DEMO
我有一项服务,peopleService
:
(function (){
angular.module('myApp')
.factory('peopleService', peopleService);
peopleService.$inject = ['$q'];
function peopleService ($q){
var people = ['Homer', 'Marge', 'Bart', 'Lisa', 'Maggie'];
// in actual project, this makes an http request
function getFamily () {
return people;
}
function getAdults (){
var family = getFamily();
return family.filter(function (person){
return person === 'Homer' || person === 'Marge';
});
}
return {
getFamily: getFamily,
getAdults: getAdults
}
}
}());
在此服务中,我的方法getAdults
使用getFamily
,过滤结果并返回数据。
在我的单元测试中,我正在尝试模拟getFamily
并查看是否正在调用该方法。现在这就是问题出现的地方......
我尝试的第一件事是删除方法并覆盖当前方法,如下所示:
beforeEach(function (){
module('myApp');
inject(function (_peopleService_){
peopleService = _peopleService_; // get the service
sinon.stub(peopleService, 'getFamily'); // stub it
});
});
然后我去测试getAdults
是否调用getFamily
方法:
it('getAdults should call "getFamily" once', function(){
peopleService.getAdults();
expect(peopleService.getFamily.calledOnce).toBe(true);
});
测试失败,并且未调用存根方法......
我调试并发现虽然该功能实际上已经改变了:
该服务仍然保留了创建服务时该方法的引用(闭包):
我最初的想法是我没有正确地存根方法。然后,我尝试使用$provide
($provide.value
)以及$injector decorator
覆盖该方法,最终得到相同的结果(闭包保持原始方法)。
解决方法是使用this
:
function getAdults (){
var family = this.getFamily(); // <-- by using this.getFamily would reference the mock
return family.filter(function (person){
return person === 'Homer' || person === 'Marge';
});
}
但是,我不明白我为什么要这样做。
简而言之,有谁知道:
this
非常感谢您的时间。
答案 0 :(得分:5)
当您在对象上存根方法时,该对象的属性将被覆盖,而不是它引用的原始函数。
以此代码为例:
function myFunction () {};
var myObj = { prop: myFunction };
myObj.prop === myFunction; // true
myObj.prop = 'changed';
typeof myFunction === 'function'; // true
myObj.prop === myFunction; // false
更改myObj.prop
并未更改原始功能,myFunction
仍然存在。但是,myObj.prop
已失去对myFunction
的引用。如果这是在sinon世界中,存根只是将myObj.prop
的引用更改为存根对象。
这就是为什么在测试同一服务中调用另一个函数的服务中的代码时,该代码需要引用服务返回的同一对象。如果您想避免在任何地方使用this
关键字,可以按照以下方式构建服务:
angular.module('myApp')
.factory('peopleService', peopleService);
peopleService.$inject = ['$q'];
function peopleService ($q){
var service = {
getFamily: getFamily,
getAdults: getAdults
};
return service;
function getFamily () {
// ...
}
function getAdults (){
var family = service.getFamily(); // <-- reference service.getFamily()
// ...
}
}