最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - jasmine test does not move into the then part of the promise - Stack Overflow

programmeradmin1浏览0评论

I would like to test this function:

    function initializeView() {
        var deferred = $q.defer();
        if(this.momentArray) {
            core.listMoments(constants.BEST_MOMENT_PREFIX, '').then(function(moments) {
                //Ommitted
                deferred.resolve(moments);      
            }, function(error) {
                console.log("ERROR");
                deferred.reject(error);
            });
        }
        else {
            deferred.resolve();
        }
        return deferred.promise;    
    };

The function calls core.listMoments:

    function listMoments(prefix, startAfter) {
        // var deferred = $q.defer();
        var promises = [];
        return awsServices.getMoments(prefix, startAfter).then(function(moments) { //Mocked
            console.log("getMoments Returned"); //Does not print
            for(var i = 0; i < moments.length; i++) {
                // moments[i].Key = constants.IMAGE_URL + moments[i].Key;
                promises.push(getMomentMetaData(moments[i]));
            }
        return $q.all(promises);
        });
    };

Here is my test function:

it('Should correctly initialize the view', function(done) {
    spyOn(awsServices, 'getMoments').and.callFake(function() {
        console.log("getMoments Has been mocked"); //This prints
        return $q.resolve(mock_moment);
    });
    service.initializeView().then(function() {
        done();
    })
});

The problem is with the awsServices 'getMoments' mock. The call to awsServices.getMoments is in the listMoments function. I would like to mock out this function but when I do it does not execute the "then" part of the promise.

So based on my console logs it would print the 'getMoments Has been mocked' log but it would not print 'getMoments Returned' log. So the function is mocked but for some reason it is not moving into the then statement and my test just times out.

I would like to test this function:

    function initializeView() {
        var deferred = $q.defer();
        if(this.momentArray) {
            core.listMoments(constants.BEST_MOMENT_PREFIX, '').then(function(moments) {
                //Ommitted
                deferred.resolve(moments);      
            }, function(error) {
                console.log("ERROR");
                deferred.reject(error);
            });
        }
        else {
            deferred.resolve();
        }
        return deferred.promise;    
    };

The function calls core.listMoments:

    function listMoments(prefix, startAfter) {
        // var deferred = $q.defer();
        var promises = [];
        return awsServices.getMoments(prefix, startAfter).then(function(moments) { //Mocked
            console.log("getMoments Returned"); //Does not print
            for(var i = 0; i < moments.length; i++) {
                // moments[i].Key = constants.IMAGE_URL + moments[i].Key;
                promises.push(getMomentMetaData(moments[i]));
            }
        return $q.all(promises);
        });
    };

Here is my test function:

it('Should correctly initialize the view', function(done) {
    spyOn(awsServices, 'getMoments').and.callFake(function() {
        console.log("getMoments Has been mocked"); //This prints
        return $q.resolve(mock_moment);
    });
    service.initializeView().then(function() {
        done();
    })
});

The problem is with the awsServices 'getMoments' mock. The call to awsServices.getMoments is in the listMoments function. I would like to mock out this function but when I do it does not execute the "then" part of the promise.

So based on my console logs it would print the 'getMoments Has been mocked' log but it would not print 'getMoments Returned' log. So the function is mocked but for some reason it is not moving into the then statement and my test just times out.

Share Improve this question asked Sep 4, 2017 at 3:23 MatTaNgMatTaNg 8958 gold badges25 silver badges42 bronze badges 4
  • getMoments will return mock_moments, but inside .then of getMoments in listMoments function it will pass each moment data to getMomentMetaData(I suspect you also need to mock this method, if it is doing actual ajax). – Pankaj Parkar Commented Sep 4, 2017 at 3:27
  • Your right I do need to mock that but it's not even getting to it right now. – MatTaNg Commented Sep 4, 2017 at 4:49
  • You could directly mock the core.listMoments to validate the behavior of initializeView function only. And test listMoments separately. – dpellier Commented Sep 4, 2017 at 9:07
  • I could, I would like to make this an integration test though. – MatTaNg Commented Sep 4, 2017 at 16:26
Add a ment  | 

2 Answers 2

Reset to default 7 +50

In order to get the .then() part of a promise to work in such a test, you need to use a $rootScope.$apply(). This is needed whether the promise is in your test code or in a referenced library that is being tested. Think of it like the flush() function for $http or $timeout calls.

The Testing example from the Angular documentation's $q page shows how to use it:

it('should simulate promise', inject(function($q, $rootScope) {
  var deferred = $q.defer();
  var promise = deferred.promise;
  var resolvedValue;

  promise.then(function(value) { resolvedValue = value; });
  expect(resolvedValue).toBeUndefined();

  // Simulate resolving of promise
  deferred.resolve(123);
  // Note that the 'then' function does not get called synchronously.
  // This is because we want the promise API to always be async, whether or not
  // it got called synchronously or asynchronously.
  expect(resolvedValue).toBeUndefined();

  // Propagate promise resolution to 'then' functions using $apply().
  $rootScope.$apply();
  expect(resolvedValue).toEqual(123);
}));

Note that they inject $rootScope.

$q promises can be synchronous (when they are resolved synchronously) and depend on digest cycles.

There should generally be no asynchronous done callback in Angular tests.

Angular tests are supposed to be synchronous, so are $q promises. In order to achieve that a digest should be triggered manually when an existing promise (the ones that is returned from getMoments and initializeView) is chained with then. If done callback is placed inside then and a digest is not triggered, this will result in spec timeout.

spyOn(awsServices, 'getMoments').and.callFake(function() {
    console.log("getMoments Has been mocked"); //This prints
    return $q.resolve(mock_moment);
});
service.initializeView();
$rootScope.$digest();

The thing that can be improved here is isolation. There are several units (methods) involved in a single test. This will affect troubleshooting when one of them fails.

Usually unit testing implies that only one unit is tested at time, while the rest are mocked or stubbed. In this case in one test service.listMoments is called and awsServices.getMoments is mocked, and in another test service.initializeView is called and service.listMoments is mocked.

发布评论

评论列表(0)

  1. 暂无评论