brazorf
brazorf

Reputation: 1951

Angular - Testing http services with httpBackend throws unexpected exception

The module definition

var module = angular.module('test', []);
module.provider('client', function() {
    this.$get = function($http) {
        return {
            foo: function() {
                return $http.get('foo');
            }
        }
    }
});

module.factory('service', ['client', function(client) {
    return {
        bar: function() {
            return client.foo();
        }
    }
}]);

Basically, client is a wrapper for http calls, and service is a wrapper around the client basic features.

I'm unit testing both the provider and the service with karma+jasmine. The provider tests run as expected, but i have a problem with the service tests:

describe('service test', function(){
    var service = null;
    beforeEach(function(){
        module('test')

        inject(function(_service_, $httpBackend, $injector) {
            service = _service_;
            $httpBackend = $injector.get('$httpBackend');
        });
    });

    it('should invoke client.foo via service.bar', function() {
        $httpBackend.expect("GET", "foo");
        service.bar();
        expect($httpBackend.flush).not.toThrow();
    });

});

I get Expected function not to throw, but it threw Error: No pending request to flush !.. When testing the provider with the same way, this test passes. Why?

Upvotes: 1

Views: 210

Answers (1)

MBielski
MBielski

Reputation: 6620

When you are testing your service, you need to mock the client and inject that mock instead of the real client. Your mock can be in the same file if you only expect to use it for testing this service or in a separate file if you'll use it again elsewhere. Doing it this way does not require the use of $httpBackend (because you are not actually making an http call) but does require using a scope to resolve the promise.

The mock client:

angular.module('mocks.clientMock', [])
    .factory('client', ['$q', function($q) {
        var mock = {
            foo: function() {
                var defOjb = $q.defer();
                defOjb.resolve({'your data':'1a'});
                return defOjb.promise;
            }
        };
        return mock;
    }]);

Using the mock:

describe('service test', function(){
    var service, mock, scope;
    beforeEach(function(){
        module('test', 'mocks.clientMock');

        inject(function(_service_, $rootScope) {
            service = _service_;
            scope = $rootScope.$new();
        });
    });

    it('should invoke client.foo via service.bar', function() {
        spyOn(client, 'foo').and.callThrough();
        service.bar();
        scope.$digest();
        expect(client.foo).toHaveBeenCalled();
    });
});

Upvotes: 1

Related Questions