19
votes

We use karma to unit test our angular services, these services contains $http calls, so we have a mocked $httpbackend in place so we can run the app without server and db. this works fine, a service can call $http("someurl?id=1234") and we get the right data back.

But when we try to do the same thing in unit tests, we can't get it to work, the promise never resolves, when it involves $http

The service:

getAllowedTypes: function (contentId) {
    var deferred = $q.defer();
    $http.get(getChildContentTypesUrl(contentId))
        .success(function (data, status, headers, config) {
            deferred.resolve(data);
        }).
        error(function (data, status, headers, config) {
            deferred.reject('Failed to retreive data for content id ' + contentId);
        });
    return deferred.promise;
}

The mocked $httpbackend

$httpBackend
   .whenGET(mocksUtills.urlRegex('/someurl'))
   .respond(returnAllowedChildren); //returns a json object and httpstatus:200

The test

it('should return a allowed content type collection given a document id', function(){

    var collection;
    contentTypeResource.getAllowedTypes(1234).then(function(result){
        collection = result;
    });

    $rootScope.$digest();

    expect(collection.length).toBe(3);
});

but collection is undefined, .then() is never called.

tried pretty much everything to get the promise to resolve, $rootScope.$apply(), $digest, $httpBacke.flush(), but nothing works

So mocked $httpBackend works when called from controllers in app, but not when services is called directly in karma unit tests

3

3 Answers

7
votes

You should not need to digest twice, since $httpBackend.flush() calls digest itself. You have to make the call, call digest to resolve the request interceptors, the call flush.

Here is a working Plnkr: http://plnkr.co/edit/FiYY1jT6dYrDhroRpFG1?p=preview

4
votes

In your case, you have to $digest twice, once for $httpBackend, and again for your own deferred.

So:

it('should return a allowed content type collection given a document id', function(){

    var collection;
    contentTypeResource.getAllowedTypes(1234).then(function(result){
        collection = result;
    });
    $httpBackend.flush();
    $rootScope.$digest();

    expect(collection.length).toBe(3);
});
4
votes

You're almost there. In your case, you just need to force a digest cycle before flushing the HTTP backend. See sample code below.

it('should return a allowed content type collection given a document id', function(){

    var collection;
    contentTypeResource.getAllowedTypes(1234).then(function(result){
        collection = result;
    });

    $rootScope.$digest();
    $httpBackend.flush();
    expect(collection.length).toBe(3);
});