Javascript 角度服务的Jasmine测试无法解析延迟调用

Javascript 角度服务的Jasmine测试无法解析延迟调用,javascript,angularjs,unit-testing,jasmine,angular-promise,Javascript,Angularjs,Unit Testing,Jasmine,Angular Promise,我是Angular的新手,我正在测试一个Angular服务,它运行一个API级别的服务,该服务封装了一系列对REST服务的调用。因为它与HTTP请求一起工作,所以服务的两个部分都与承诺一起工作,而且似乎工作得很好,但是我很难对承诺的行为进行任何测试 我的服务代码的相关部分(大大简化)如下所示: angular.module('my.info') .service('myInfoService', function (infoApi, $q) { infoLoaded: false,

我是Angular的新手,我正在测试一个Angular服务,它运行一个API级别的服务,该服务封装了一系列对REST服务的调用。因为它与HTTP请求一起工作,所以服务的两个部分都与承诺一起工作,而且似乎工作得很好,但是我很难对承诺的行为进行任何测试

我的服务代码的相关部分(大大简化)如下所示:

angular.module('my.info')
 .service('myInfoService', function (infoApi, $q) {
    infoLoaded: false,
    allInfo: [],
    getInfo: function () {
        var defer = $q.defer();
        if (infoLoaded) {
            defer.resolve(allInfo);
        } else {
            infoApi.getAllInfo().then(function (newInfo) {
                allInfo = newInfo;
                infoLoaded = true;
                defer.resolve(allInfo);
            });
        }
        return defer.promise;
    }
});
   describe("Info Service ",
     function() {
        var infoService, infoRequestApi, $q;
        beforeEach(module("my.info"));
        beforeEach(function() {
            module(function($provide) {
                infoRequestApi = {
                   requestCount: 0,
                   getAllInfo: function() {
                       var defer = $q.defer(); 
                       this.requestCount++;
                       defer.resolve( [ "info 1", "info 2" ] );
                       return defer.promise;
                   }
                };
                $provide.value("infoApi", infoRequestApi);
            });
            inject(function( _myInfoService_, _$q_ ) {
                 infoService = _myInfoService_,
                 $q = _$q_;
            });
        });

        it("should not fail in the middle of a test", function(done) {
            infoService.getInfo().then( function(infoResult) {
                  // expectation checks.
                  expect( true ).toBeTrue();
              }).finally(done);
        });
   });
it("should not fail in the middle of a test", inject(function($rootScope, infoApi, $q) {
  var deferred = $q.defer();
  spyOn(infoApi, 'getAllInfo').and.returnValue(deferred.promise);
  var actualResult = null;
  var expectedResult = ["info 1", "info 2"];

  infoService.getInfo()
    .then(function(infoResult) { actualResult = infoResult; });

  deferred.resolve(expectedResult);
  $rootScope.$apply();
  expect(actualResult).toBe(expectedResult);
}));
当我设置我的模拟时,我有如下内容:

angular.module('my.info')
 .service('myInfoService', function (infoApi, $q) {
    infoLoaded: false,
    allInfo: [],
    getInfo: function () {
        var defer = $q.defer();
        if (infoLoaded) {
            defer.resolve(allInfo);
        } else {
            infoApi.getAllInfo().then(function (newInfo) {
                allInfo = newInfo;
                infoLoaded = true;
                defer.resolve(allInfo);
            });
        }
        return defer.promise;
    }
});
   describe("Info Service ",
     function() {
        var infoService, infoRequestApi, $q;
        beforeEach(module("my.info"));
        beforeEach(function() {
            module(function($provide) {
                infoRequestApi = {
                   requestCount: 0,
                   getAllInfo: function() {
                       var defer = $q.defer(); 
                       this.requestCount++;
                       defer.resolve( [ "info 1", "info 2" ] );
                       return defer.promise;
                   }
                };
                $provide.value("infoApi", infoRequestApi);
            });
            inject(function( _myInfoService_, _$q_ ) {
                 infoService = _myInfoService_,
                 $q = _$q_;
            });
        });

        it("should not fail in the middle of a test", function(done) {
            infoService.getInfo().then( function(infoResult) {
                  // expectation checks.
                  expect( true ).toBeTrue();
              }).finally(done);
        });
   });
it("should not fail in the middle of a test", inject(function($rootScope, infoApi, $q) {
  var deferred = $q.defer();
  spyOn(infoApi, 'getAllInfo').and.returnValue(deferred.promise);
  var actualResult = null;
  var expectedResult = ["info 1", "info 2"];

  infoService.getInfo()
    .then(function(infoResult) { actualResult = infoResult; });

  deferred.resolve(expectedResult);
  $rootScope.$apply();
  expect(actualResult).toBe(expectedResult);
}));
任何同步测试都可以顺利通过,但当我尝试运行任何类似的测试时,我会收到一条消息,上面说:
错误:超时-在jasmine指定的超时时间内未调用异步回调。默认超时时间间隔。


似乎Angular.Mocks处理延迟结果的方式导致了失败。当我逐步完成测试时,mock对象的
defer
变量设置正确,但是服务中的
then
语句从未被调用。我哪里做错了?

简短回答

你必须开始一个消化周期。您可以使用
$rootScope.$apply()
实现这一点

注意

我不会试图以您现在的方式创建一个假的
infoRequestApi
服务。您还应该注入该服务,并监视其功能。例如,类似这样的事情:

angular.module('my.info')
 .service('myInfoService', function (infoApi, $q) {
    infoLoaded: false,
    allInfo: [],
    getInfo: function () {
        var defer = $q.defer();
        if (infoLoaded) {
            defer.resolve(allInfo);
        } else {
            infoApi.getAllInfo().then(function (newInfo) {
                allInfo = newInfo;
                infoLoaded = true;
                defer.resolve(allInfo);
            });
        }
        return defer.promise;
    }
});
   describe("Info Service ",
     function() {
        var infoService, infoRequestApi, $q;
        beforeEach(module("my.info"));
        beforeEach(function() {
            module(function($provide) {
                infoRequestApi = {
                   requestCount: 0,
                   getAllInfo: function() {
                       var defer = $q.defer(); 
                       this.requestCount++;
                       defer.resolve( [ "info 1", "info 2" ] );
                       return defer.promise;
                   }
                };
                $provide.value("infoApi", infoRequestApi);
            });
            inject(function( _myInfoService_, _$q_ ) {
                 infoService = _myInfoService_,
                 $q = _$q_;
            });
        });

        it("should not fail in the middle of a test", function(done) {
            infoService.getInfo().then( function(infoResult) {
                  // expectation checks.
                  expect( true ).toBeTrue();
              }).finally(done);
        });
   });
it("should not fail in the middle of a test", inject(function($rootScope, infoApi, $q) {
  var deferred = $q.defer();
  spyOn(infoApi, 'getAllInfo').and.returnValue(deferred.promise);
  var actualResult = null;
  var expectedResult = ["info 1", "info 2"];

  infoService.getInfo()
    .then(function(infoResult) { actualResult = infoResult; });

  deferred.resolve(expectedResult);
  $rootScope.$apply();
  expect(actualResult).toBe(expectedResult);
}));
重构

此外,您的代码可以简化一点(未经测试,但接近我期望看到的)


非常感谢您的回答和重构建议。我怀疑嘲弄的方法有点冗长!我现在唯一的问题是,如果我有两个服务层,看起来好像我需要为每个构造函数注入一个mock,但这更整洁、更惯用。@glenatron没问题。您只需要模拟测试服务中使用的依赖项。您不需要模拟它们的依赖关系,因此层的问题是没有意义的。您是对的,我实际上遇到了Visual Studio的Chutzpah测试运行程序的问题,这导致了虚假的失败。同样的测试在其他测试运行者下也可以正常工作。