角度单元测试 - 在同一服务中模拟方法/闭包

时间:2014-12-06 04:55:11

标签: angularjs unit-testing sinon

我目前正在尝试使用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);
});

测试失败,并且未调用存根方法......

我调试并发现虽然该功能实际上已经改变了: enter image description here

该服务仍然保留了创建服务时该方法的引用(闭包):

enter image description here

我最初的想法是我没有正确地存根方法。然后,我尝试使用$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
  • 的情况下模拟同一服务中的其他方法
  • 如何在单元测试中模拟闭包变量

非常感谢您的时间。

1 个答案:

答案 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()
    // ...
  }
}