【问题标题】:Jasmine test for Angular service does not resolve deferred callAngular 服务的 Jasmine 测试无法解决延迟调用
【发布时间】:2017-01-08 09:09:15
【问题描述】:

我对 Angular 很陌生,我正在测试一个 Angular 服务,该服务运行一个 API 级别的服务,该服务包装了对 REST 服务的一堆调用。因为它正在处理 HTTP 请求,所以服务的两个部分都在处理 Promise,而且它似乎工作正常,但我无法让任何对 Promise 行为的测试正常工作。

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

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

任何同步测试都可以通过,但是当我尝试运行任何这样的测试时,我会收到一条消息:Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

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

【问题讨论】:

    标签: javascript angularjs unit-testing jasmine angular-promise


    【解决方案1】:

    简答

    您必须开始一个摘要循环。你可以通过$rootScope.$apply() 做到这一点。

    it("should not fail in the middle of a test", inject(function($rootScope) {
      var actualResult = null;
      infoService.getInfo()
        .then(function(infoResult) { actualResult = infoResult; });
      $rootScope.$apply();
      expect(actualResult).toEqual(["info 1", "info 2"]);
    }));
    

    注意

    我不会像您那样尝试创建虚假的infoRequestApi 服务。您也应该注入该服务,并监视其功能。例如,这样的事情:

    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) {
         return {
           infoLoaded: false,
           allInfo: [],
           getInfo: function () {
             var self = this;
             return this.infoLoaded ? $q.resolve(this.allInfo) :
               infoApi.getAllInfo().then(function (newInfo) {
                 self.allInfo = newInfo;
                 self.infoLoaded = true;
               });
         }
      };
    });
    
    describe("Info Service ", function() {
      var infoService, infoApi, $q, $rootScope;
    
      beforeEach(module("my.info"));
      beforeEach(inject(function( _myInfoService_, _$q_ _$rootScope_, _infoApi_) {
        infoService = _myInfoService_,
        $q = _$q_;
        $rootScope = _$rootScope_;
        infoApi = _infoApi_;
      });
    
      it("should do something", function() { // update message
        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);
      });
    });
    

    【讨论】:

    • 谢谢你的好答案和重构建议。我怀疑嘲笑的方法有点啰嗦!我现在唯一的问题是,如果我有几个服务层,看起来好像我需要为每个构造函数注入一个模拟,但这是更整洁和更惯用的方法。
    • @glenatron 没问题。您只需要模拟被测服务中使用的依赖项。你不需要模拟它们的依赖关系,所以层的问题没有实际意义。
    • 你是对的,我实际上遇到了 Visual Studio 的 Chutzpah 测试运行程序的问题,这导致了虚假故障。相同的测试在其他测试运行器下运行良好。
    猜你喜欢
    • 2014-11-16
    • 1970-01-01
    • 2016-06-01
    • 1970-01-01
    • 2018-10-25
    • 2018-12-04
    • 1970-01-01
    • 1970-01-01
    • 2019-05-02
    相关资源
    最近更新 更多