Angularjs 使用Jasmine测试立即解析$.defer()

Angularjs 使用Jasmine测试立即解析$.defer(),angularjs,jasmine,promise,synchronous,Angularjs,Jasmine,Promise,Synchronous,如果您在Angular中测试代码,它使用$q并立即解析如下: angular.module('MyApp.myModule', ['ng']) .service('someService', function($q) { this.task = function() { var deferred = $q.defer(); deferred.resolve('some value'); return deferred.promise; };

如果您在Angular中测试代码,它使用$q并立即解析如下:

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');
            });
          });
        });
      });
    });
  });
});

失败的原因是,尽管代码在内部似乎是同步的,
$q
使用
$scope
中的
$evalAsync
将工作延迟到将来的调用堆栈。由于
$q
没有像
$httpBackend
$timeout
$interval
这样的
$flush
方法,因此需要调用
$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');
            });
          });
        });
      });
    });
  });
});