Les Paul
Les Paul

Reputation: 1278

Unit testing $q promise within Angular service - Karma, Jasmine

I am trying to unit test an Angular service with Karma and Jasmine. I'm making I've seen a number of tutorials on how to unit test a service call from a controller, but I wish to unit test within the service itself. UsersService.findById() makes an async call to find an ID within an ID, but I can't figure out how to write a passing test. I'm close, but I need a little help. Here is the function within UsersService

users.service.js

        Users.findById = function(id){
            var deferred = $q.defer();

            deferred.resolve(userList.find(function(user){
                return user.id == id;
            }));

            return deferred.promise;
        };

Here is the spec:

users.service.spec.js

describe("Users Factory", function(){
    var UsersService, $q, $rootScope, $provide, deferred, mockedDeferred;

    var userList = [
        { id: 1, name: "Richard Hendricks", email: "[email protected]", phone: 4085550011, pokemon: { isPresent: true, name: "eevee"}, icon: { isPresent: false, name: null} },
        { id: 2, name: "Erlich Bachman", email: "[email protected]", phone: 4155552233, pokemon: { isPresent: true, name: "celebi"}, icon: { isPresent: false, name: null} },
        { id: 3, name: "Gavin Belson", email: "[email protected]", phone: 9165554455, pokemon: { isPresent: true, name: "snorlax"}, icon: { isPresent: false, name: null} }
    ];

    var singleUser = { id: 2, name: "Erlich Bachman", email: "[email protected]", phone: 4155552233, pokemon: { isPresent: true, name: "celebi"}, icon: { isPresent: false, name: null} };

    function mockQ(){
        deferred = $q.defer();
        spyOn(UsersService, "findById");
        return deferred;
    }

    beforeEach(angular.mock.module("testing_app"));
    beforeEach(angular.mock.module(function(_$provide_){
        $provide = _$provide_;
    }));

    beforeEach(inject(function(_UsersService_, _$q_, _$rootScope_){
        UsersService = _UsersService_;
        $q = _$q_;
        $rootScope = _$rootScope_;
    }));

    describe("Users.findById()", function(){

        beforeEach(function(){
            mockedDeferred = mockQ();
        });

        it("should exist", function(){
            expect(UsersService.findById).toBeDefined();
        });

        it("should equal singleUser", function(){
            UsersService.findById(2);
            deferred.resolve(userList.find(function(user){
                return user.id == 2;
            }));
            expect(UsersService.findById).toHaveBeenCalled();
            expect(deferred.promise).toEqual(singleUser);
        });
    });
});

Here's what I have in my console:

Erlich Bachman, this is your mom. You are not my baby

I'm close, but how do I get the promise to equal the singleUser variable?

Upvotes: 0

Views: 1610

Answers (1)

yeiniel
yeiniel

Reputation: 2456

You are testing the UsersService service. Therefore your test cases should exercise the service and verify it behave correctly. With this in mind checking if findById() as been called on the same function (the test) that call it do not provide any verification and is superfluous and unnecessary. In this particular test case a more productive check will be to verify that the method return a Promise like object (see here for more info on that). The other productive check will be to verify the response value (once promise has been resolved) of the method for known inputs. For the latter what you need to do is call $rootScope.$apply(); before checking the resolved value of the promise against the expected one (see the testing section on promise from the angularjs docs for an example on that). Once we apply this recommendations the tests for findById() will be as follows:

describe("Users.findById()", function(){

    it("should exist", function(){
        expect(UsersService.findById).toBeDefined();
    });

    it("should produce a promise", function(){
        var p = UsersService.findById(2);

        expect(p instanceof $q.resolve().constructor).toBeTruthy();
    });

    it("should equal singleUser", function(){
        var p = UsersService.findById(2), resolvedValue;

        p.then(function(value) { resolvedValue = value; });

        $rootScope.$apply();

        expect(resolvedValue).toEqual(singleUser);
    });
});

On some cases the service under test is more complex and encapsulates other low level services. On those cases you need to spy or mock those lower level services in order to verify that the higher level service under test behaves as expected by checking it calls the lower level services.

Upvotes: 2

Related Questions