Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

$rootScope.digest not completing a promise in Jasmine

We currently are trying to test our angular services which use promises to return values to the controllers. The issue is that the functions we attach to the .then do not get called in Jasmine.

We found that adding $rootScope.digest() to the function after the promise is returned allows synchronous promises to be called however it still does not work for Asynchronous promises.

The code so far is

    beforeEach(inject(function (Service, $rootScope)
    {
        service = Service;
        root = $rootScope;
    }));

    it('gets all the available products', function (done)
    {
        service.getData().then(function (data)
        {
            expect(data).not.toBeNull();
            expect(data.length).toBeGreaterThan(0);
            done();
        });
        root.$digest();
    });

In this case the promise gets called fine but if it is asynchronous it will not be called because the promise is not ready for "digestion" by the time the root.$digest() is called.

Is there some way to tell when a promise is after getting resolved so that i can call digest? Or maybe something that would do that automatically? Thanks ;)

Partial of the Service we must test(error handling removed):

var app = angular.module('service', []);

/**
 * Service for accessing data relating to the updates/downloads
 * */
app.factory('Service', function ($q)
{
     ... init

    function getData()
    {
        var deffered = $q.defer();

        var processors = [displayNameProc];

        downloads.getData(function (err, data)
        {
            chain.process(processors, data, function (err, processedData)
            {
                deffered.resolve(processedData);
            });
        });

        return deffered.promise;
    }
    ...

In the case where there is a problem is when service.getData is async the promise is resolved but the function attached to that promise from the test code is not called because the root.$digest has already been called. Hope this gives more info

Workaround

var interval;
beforeEach(inject(function ($rootScope)
{
    if(!interval)
    {
        interval = function ()
        {
            $rootScope.$digest();
        };
        window.setInterval(interval, /*timing*/);
    }
}));

I ended up using the workaround above to call the digest repeatedly so each layer of promise would get a chance to run...its not ideal or pretty but it works for the test...

like image 358
BrendanM Avatar asked Sep 22 '15 13:09

BrendanM


1 Answers

You should move your asserts outside of the then I believe:

beforeEach(inject(function (Service, $rootScope)
{
    service = Service;
    root = $rootScope;
}));

it('gets all the available products', function (done)
{
    var result;
    service.getData().then(function (data)
    {
        result = data;
        done();
    });

    root.$digest();

    expect(result ).not.toBeNull();
    expect(result .length).toBeGreaterThan(0);
});

Update

Forked the plunkr out of the comment below to show how you can test an async call.

Added a $timeout to the service:

var app = angular.module('bad-service', []);

app.service('BadService', function ($q, $interval, $timeout)
{
    this.innerPromise = function(){
      var task = $q.defer();
      console.log('Inside inner promise');

      $timeout(function() {
        console.log('Inner promise timer fired');
        task.resolve();
      }, 500);
      // $interval(function(){

      // }, 500, 1);
      return task.promise;
    }
});

And I am flushing the $timeout before asserting:

beforeEach(module('test'));


describe('test', function(){
  var service, root, $timeout;

    beforeEach(inject(function (Service, $rootScope, _$timeout_)
    {
      $timeout = _$timeout_;
        console.log('injecting');
        service = Service;
        root = $rootScope;
    }));

    /**
    Test one
    */
    it('Should work', function(done){
      console.log('Before service call');
      service.outerPromise().then(function resolve(){
        console.log('I should be run!');
        expect(true).toBeTruthy();
        done();
      });
      // You will have to use the correct "resolve" here. For example when using:
      // $httpbackend - $httpBackend.flush();
      // $timeout- $timeout.flush();
      $timeout.flush();

      console.log('After service call');
    });
});
like image 111
Daan van Hulst Avatar answered Jan 05 '23 00:01

Daan van Hulst