Promise resolved too late in angularjs jasmine test

雨燕双飞 提交于 2019-12-23 19:13:52

问题


I have the following jasmine test written with ng-describe, run with karma.

(I am using 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");
    });
  }
});

The test would fail because the promise is resolved too late:

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)

After too long investigation, I figured it that if I use $q instead of Promise, it will work fine.

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

I'm wondering however, why is this the case, and can I change something in my test to use Promise instead of $q to make the test pass?

I've read in various places about $rootScope.$apply() but wherever I put it, it still doesn't work for me.


回答1:


Angular $q testing is synchronous, which is a huge advantage. Once scope $digest() was called, the one can expect that all $q promise chain handlers were called also.

Promises in general (including ES6 implementation) are asynchronous by design, on the other hand. Once the promise was resolved, its handlers will be called on the next tick. So pleasant Angular testing is not that synchronous anymore:

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 can be mocked for testing purposes to be synchronous, mock-promises may be used in this case.

And there is jasmine-co which is intended to make asynchronous testing in Jasmine easier (and it actually does) with ES6 generators and co, I'm not aware how good it performs with ng-describe.



来源:https://stackoverflow.com/questions/34228045/promise-resolved-too-late-in-angularjs-jasmine-test

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!