测试立即解决了Jasmine的$ .defer()问题

时间:2014-04-24 11:38:48

标签: angularjs jasmine promise synchronous

如果您正在使用$ q测试Angular中的代码并立即解析,例如;

angular.module('MyApp.myModule', ['ng'])
  .service('someService', function($q) {
    this.task = function() {
      var deferred = $q.defer();
      deferred.resolve('some value');
      return deferred.promise;
    };
  });

可能如下使用;

function(someService) {
  someService.task().then(function() {
    console.log('resolved');
  });
}

您可能会发现它在您的应用程序中按预期运行,但在测试中失败;

PhantomJS 1.9.7 (Mac OS X) MyApp.myModule someService someService.task when invoked returned promise when invoked should call our handler immediately FAILED
  Expected spy onTaskComplete to have been called with [ 'some value' ] but it was never called.

以下是上述模块的示例测试;

describe('MyApp.myModule', function() {
  describe('someService', function() {
    beforeEach(function() {
      var suite = this;
      module('MyApp.myModule');
      suite.injectService = function() {
        inject(function(someService) {
          suite.someService = someService;
        });
      };
    });
    describe('when instantiated', function() {
      beforeEach(function() {
        this.injectService();
      });
      it('should expose the expected API', function() {
        expect(typeof this.someService.task).toEqual('function');
      });
    });
    describe('someService.task', function() {
      describe('when invoked', function() {
        beforeEach(function() {
          this.injectService();
          this.taskPromise = this.someService.task();
        });
        it('should return a promise', function() {
          expect(typeof this.taskPromise.then).toEqual('function');
        });
        describe('returned promise', function() {
          describe('when invoked', function() {
            beforeEach(function() {
              this.onTaskComplete = jasmine.createSpy('onTaskComplete');
              this.taskPromise.then(this.onTaskComplete);
            });
            it('should call our handler immediately', function() {
              expect(this.onTaskComplete).toHaveBeenCalledWith('some value');
            });
          });
        });
      });
    });
  });
});

1 个答案:

答案 0 :(得分:5)

此失败的原因是 - 尽管代码似乎是同步内部$q使用$evalAsync中的$scope将工作推迟到将来的调用堆栈。由于$q没有像flush$httpBackend$timeout这样的$interval方法,因此需要调用$rootScope.$digest()才能获得相同的结果

PhantomJS 1.9.7 (Mac OS X): Executed 3 of 3 SUCCESS (0.451 secs / 0.01 secs)

这是更新的示例测试;

describe('MyApp.myModule', function() {
  describe('someService', function() {
    beforeEach(function() {
      var suite = this;
      module('MyApp.myModule');
      inject(function($rootScope) {
        suite.$rootScope = $rootScope;
      });
      suite.injectService = function() {
        inject(function(someService) {
          suite.someService = someService;
        });
      };
    });
    describe('when instantiated', function() {
      beforeEach(function() {
        this.injectService();
      });
      it('should expose the expected API', function() {
        expect(typeof this.someService.task).toEqual('function');
      });
    });
    describe('someService.task', function() {
      describe('when invoked', function() {
        beforeEach(function() {
          this.injectService();
          this.taskPromise = this.someService.task();
        });
        it('should return a promise', function() {
          expect(typeof this.taskPromise.then).toEqual('function');
        });
        describe('returned promise', function() {
          describe('when invoked', function() {
            beforeEach(function() {
              this.onTaskComplete = jasmine.createSpy('onTaskComplete');
              this.taskPromise.then(this.onTaskComplete);
              this.$rootScope.$digest();
            });
            it('should call our handler immediately', function() {
              expect(this.onTaskComplete).toHaveBeenCalledWith('some value');
            });
          });
        });
      });
    });
  });
});