测试具有依赖关系的AngularJS服务

时间:2014-07-31 19:03:27

标签: javascript angularjs testing jasmine

在为模拟依赖项创建提供程序时遇到了麻烦,因为它需要使用$q,这是angular中的另一个服务,并且在设置提供程序时无法访问这些服务。

想象一下,我们有一个我们想要测试的工厂:

angular.module('myApp').factory('MyFactory', function (MyDependency, $q) {
  return {
    doSomething: function () {
      var deferred = $q.defer();
      MyDependency.doAction().then(function (response) {
        deferred.resolve(response);
        // other events
      }, function (error) {
        deferred.reject(error);
        // other events
      });

      return deferred.promise;
    }
  }
});

以下单元测试:

describe('Service: MyFactory', function () {
  var myDependency, myFactory;

  beforeEach(module('myApp'));

  // The problem is here, as $q cannot be instantiated
  // when setting up providers, and our mock service we are 
  // creating as the dependency for MyFactory requires $q
  beforeEach(module(function ($provide, $q) { 
    var promise = $q.defer().promise;
    myDependency = jasmine.createSpyObj('MyDependency', ['open']);
    myDependency.open.andReturn(promise);
    $provide.value('MyDependency', {
      doAction: myDependency.open
    });
  }));

  beforeEach(inject(function (MyFactory) {
    myFactory = MyFactory;
  }));

  describe('MyDependency.doAction should be called', function () {
    myFactory.doSomething();
    expect(myDependency.open).toHaveBeenCalled();
    // expect other events
  });
});

MyDependency有一个函数open,我们需要使用自定义承诺来监视和覆盖该方法,我们将控制正在解析和拒绝的数据。我们可以轻松创建将注入MyFactory的模拟依赖项,但是如何在此阶段访问其他服务,例如$q

我提出的唯一合理的解决方案就是像这样设置提供程序,但与promise.reject()promise.resolve相比,它为我们提供了更少的控制权和更多的解决方案来处理成功与失败。 )

beforeEach(module(function ($provide) {
  myDependency = jasmine.createSpyObj('MyDependency', ['doAction']);
  myDependency.doAction.andCallFake(function (){
    return {
      then: function (success, err){
        success.call(this);
      }
    };
  });
  $provide.value('MyDependency', {
    open: myDependency.open
  });
}));

1 个答案:

答案 0 :(得分:2)

假设

  • 我对MyDependency服务几乎一无所知
  • 是承诺
  • 我只能测试解决或拒绝的行为

最奇怪的一行是$provide.value('MyDependency', {});。这是概念证明,欢迎提出任何意见。

实施的第一次修订

(function() {
  angular.module('myApp', []).factory('MyFactory', function(MyDependency, $q) {
    return {
      doSomething: function() {
        var deferred = $q.defer();

        MyDependency.doAction().then(function(response) {
          deferred.resolve(response);
        }, function(error) {
          deferred.reject(error);
        });

        return deferred.promise;
      }
    };
  });
})();

describe('myApp', function() {

  var MyFactory, MyDependency = {},
    $q, scope;

  beforeEach(function() {
    module('myApp');
  });

  beforeEach(function() {
    module(function($provide) {
      $provide.value('MyDependency', {});
    });
  });

  beforeEach(inject(function(_MyFactory_, _$q_, $rootScope) {
    MyFactory = _MyFactory_;
    $q = _$q_;
    scope = $rootScope.$new();
  }));

  describe('MyDependency', function() {
    var MyDependencyDefer;

    beforeEach(inject(function(MyDependency, $q) {
      MyDependencyDefer = $q.defer();
      MyDependency.doAction = jasmine.createSpy('MyDependency.doAction').and.returnValue(MyDependencyDefer.promise);
    }));

    it('resolves doAction()', function() {
      var stubSuccess = 'mock data';
      var doSomethingDefer = MyFactory.doSomething();
      MyDependencyDefer.resolve(stubSuccess);

      doSomethingDefer.then(function(r) {
        expect(r).toBe(stubSuccess);
      });

      scope.$digest();
    });

    it('rejects doAction()', function() {
      var stubError = 'reason error';
      var doSomethingDefer = MyFactory.doSomething();
      MyDependencyDefer.reject(stubError);

      doSomethingDefer.catch(function(r) {
        expect(r).toBe(stubError);
      });

      scope.$digest();
    });
  });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-mocks.js"></script>

实施的第二次修订(经过两年的开发)

(() => {
  angular.module('myApp', []).factory('MyFactory', ['MyDependency', MyDependency => {
    return {
      doSomething: () => MyDependency.doAction()
    }
  }])
})()

describe('MyFactory.doSomething() returns MyDependency.doAction()', () => {
  'use strict';

  const promiseResponse = {
    succ: 'success',
    err: 'error'
  }
  let MyFactory, MyDependency = {},
    $q, scope

  beforeEach(module('myApp'));

  beforeEach(() => {
    module(['$provide', $provide => {
      $provide.value('MyDependency', {})
    }]);
  });

  beforeEach(inject(['MyFactory', '$q', '$rootScope', (_MyFactory_, _$q_, $rootScope) => {
    MyFactory = _MyFactory_
    $q = _$q_
    scope = $rootScope.$new()
  }]));

  describe('MyDependency.doAction() returns promise', () => {
    let MyDependencyDefer, doSomethingDefer

    beforeEach(inject(['MyDependency', '$q', (MyDependency, $q) => {
      MyDependencyDefer = $q.defer()
      MyDependency.doAction = jasmine.createSpy('MyDependency.doAction').and.returnValue(MyDependencyDefer.promise)
    }]))

    beforeEach(() => {
      doSomethingDefer = MyFactory.doSomething()
    })

    it('that can be resolved', done => {
      MyDependencyDefer.resolve(promiseResponse.succ)

      doSomethingDefer.then(r => {
        expect(r).toBe(promiseResponse.succ)
        done()
      });

      scope.$digest()
    });

    it('that can be rejected', done => {
      MyDependencyDefer.reject(promiseResponse.err)

      doSomethingDefer.catch(r => {
        expect(r).toBe(promiseResponse.err)
        done()
      })

      scope.$digest()
    })
  })
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-mocks.js"></script>