承诺在angularjs jasmine测试中解决得太晚了

时间:2015-12-11 16:21:42

标签: angularjs unit-testing jasmine

我使用ng-describe编写了以下jasmine测试,并使用karma运行。

(我正在使用es6-promise polyfill for PhantomJS)

var myModule = angular.module('MyModule', []);
myModule.service('MyService', [function() {
  return {
    getItems: function() {
      // this will be spied and mocked
    }
  };
}]);

myModule.controller('MyController', ['$scope', 'MyService',
  function($scope, MyService) {
    $scope.items = [];

    $scope.refreshItems = function() {
      MyService.getItems().then(
        function ok(items) {
          $scope.items = items;
          console.log('OK, items.length = ' + items.length);
        },
        function fail(reason) {
          console.error('FAIL')
        }).catch(console.error.bind(console));
    };
  }
]);

ngDescribe({
  name: "MyController test",
  modules: ['MyModule'],
  inject: ['MyService', '$rootScope', '$q'],
  controllers: 'MyController',
  tests: function(deps) {

    function getPromise(val) {
      return new Promise(function(resolve, reject) {
        resolve(val);
      });
    }

    it('updates $scope.items', function() {
      spyOn(deps.MyService, 'getItems').and.returnValue(getPromise([4, 5, 6]));
      deps.MyController.refreshItems();
      deps.$rootScope.$digest();

      expect(deps.MyService.getItems).toHaveBeenCalled();
      expect(deps.MyController.items.length).toBe(3);
      console.log("END OF TEST");
    });
  }
});

测试会失败,因为承诺得到解决太晚了:

LOG: 'END OF TEST'
PhantomJS 1.9.8 (Windows 7 0.0.0) MyController test updates $scope.items FAILED
        Expected 0 to be 3.
            at d:/git/myproject/test/controllers/ItmngtControllerTest.js:49
LOG: 'OK, items.length = 3'
PhantomJS 1.9.8 (Windows 7 0.0.0): Executed 37 of 37 (1 FAILED) (0.085 secs / 0.26 secs)

经过长时间的调查后,我发现如果我使用$q代替Promise,它会正常工作。

    function getPromise(val) {
      var deferred = deps.$q.defer();
      deferred.resolve(val);
      return deferred.promise;
    }

我想知道,为什么会出现这种情况,我可以在测试中更改某些内容以使用Promise代替$q来使测试通过吗?

我在不同的地方读过有关$rootScope.$apply()的内容,但无论我把它放在哪里,它仍然不适用于我。

1 个答案:

答案 0 :(得分:2)

Angular $q测试是同步的,这是一个巨大的优势。调用范围$digest()后,可以预期所有$q承诺链处理程序也会被调用。

另一方面,一般的承诺(包括ES6实现)在设计上是异步的。一旦承诺得到解决,它的处理程序将在下一个滴答中被调用。如此令人愉快的Angular测试不再是那种同步:

it('updates $scope.items', function (done) {
  ...
  setTimeout(() => {
    expect(deps.MyService.getItems).toHaveBeenCalled();
    expect(deps.MyController.items.length).toBe(3);
    console.log("END OF TEST");
    done();
  });
});
可以将

Promise模拟为同步测试目的,在这种情况下可以使用mock-promises

还有jasmine-co旨在使用ES6生成器和co更容易(实际上)在Jasmine中进行异步测试,我不知道它对{的执行情况有多好{1}}。