Mocking asynchronous service function with angular / karma / jasmine

The test in this code fails. I cannot successfully test the return of an async function.

describe('mocking services', function () {

    var someService, deferred;

    beforeEach(function () {

        module(function($provide){
            $provide.factory('someService', function($q){
                return{
                    trySynch: function(){
                        return 33;
                    },
                    tryAsynch: function(){
                        deferred = $q.defer();
                        return deferred.promise;
                    }
                };
            });
        });

        inject(function (_someService_) {
            someService = _someService_;
        });
    });

    it('should be able to test values from both functions', function () {
        expect(someService.trySynch()).toEqual(33);

        var retVal;
        someService.tryAsynch().then(function(r){
            retVal = r;
        });
        deferred.resolve(44);
        expect(retVal).toEqual(44);
    });

});

      

I get the following error on startup:

Chrome 36.0.1985 (Mac OS X 10.9.4) mocking services should be able to test values from both functions FAILED
        Expected undefined to equal 44.
        Error: Expected undefined to equal 44.
            at null.<anonymous> (/Users/selah/WebstormProjects/macrosim-angular/test/spec/services/usersAndRoles-service-test.js:34:24)

      

How can I take this test?

+3


source to share


3 answers


When mocking async calls with $ q, you need to use $rootScope.$apply()

because of the way $ q is implemented.

In particular, a method is .then

not called synchronously, it must always be asynchronous, regardless of whether it was called synchronous or asynchronous.

For this, $ q integrates with $ rootScope. Therefore, in your unit tests, you need to notify $ rootScope that something has changed (i.e. - start the digest loop). To do this, you call$rootScope.$apply()



See here (in particular, "Differences between Kris Kowal Q and $ q Section")

The working code looks like this:

describe('mocking services', function () {

    var someService, deferred, rootScope;

    beforeEach(function () {

        module(function($provide){
            $provide.factory('someService', function($q){
                return{
                    trySynch: function(){
                        return 33;
                    },
                    tryAsynch: function(){
                        deferred = $q.defer();
                        return deferred.promise;
                    }
                };
            });
        });

        inject(function ($injector) {
            someService = $injector.get('someService');
            rootScope = $injector.get('$rootScope');
        });
    });

    it('should be able to test values from both functions', function () {
        expect(someService.trySynch()).toEqual(33);

        var retVal;
        someService.tryAsynch().then(function(r){
            retVal = r;
        });
        deferred.resolve(44);
        rootScope.$apply();
        expect(retVal).toEqual(44);
    });

});

      

+2


source


$q

the deferred decision is still executed asynchronously.

A quick test, albeit in an older Angular version: http://plnkr.co/edit/yg2COXG0TWBYniXOwJYb



This test should work:

it('should be able to test values from both functions', function (done) {

    expect(someService.trySynch()).toEqual(33);

    someService.tryAsynch().then(function(r){
        expect(r).toEqual(44);
        done();
    });

    deferred.resolve(44);
});

      

0


source


If I run rootScope.$apply()

before an expect clause that tests my async function, then the test succeeds. Also, it fails if I supply the wrong value as I would expect.

So my test is functional, but I however don't understand why rootScope. $ apply () is very important here, so if anyone wants to copy my code and provide an explanation, I will happily mark your answer as the correct answer!

My working test code looks like this:

describe('mocking services', function () {

    var someService, deferred, rootScope;

    beforeEach(function () {

        module(function($provide){
            $provide.factory('someService', function($q){
                return{
                    trySynch: function(){
                        return 33;
                    },
                    tryAsynch: function(){
                        deferred = $q.defer();
                        return deferred.promise;
                    }
                };
            });
        });

        inject(function ($injector) {
            someService = $injector.get('someService');
            rootScope = $injector.get('$rootScope');
        });
    });

    it('should be able to test values from both functions', function () {
        expect(someService.trySynch()).toEqual(33);

        var retVal;
        someService.tryAsynch().then(function(r){
            retVal = r;
        });
        deferred.resolve(44);
        rootScope.$apply();
        expect(retVal).toEqual(44);
    });

});

      

0


source







All Articles