twDuke
twDuke

Reputation: 927

What is wrong with this AngularJS/Jasmine unittest that tests a service using promises?

Been trying to wrap my head around this angular-mocks jasmine testing for a couple of days now.. Got some basic stuff working but when wanting to spy and assert stuff on mocks i get stuck.

TEST CODE

describe("My service tests: calls correct urls and resolves", function () {
var $myService, $httpBackend, $q, deffered;
beforeEach(module('myModule', function ($provide) {
    var mockEndpointService = {
        getApiEndpoint: function () {
            return 'mockEndPoint';
        }
    };

    $provide.value('$endpointService', mockEndpointService);
}));
beforeEach(inject(function (_$myService_) {
    $myService= _myService_;
}));
beforeEach(inject(function ($injector) {
    $httpBackend = $injector.get('$httpBackend');
    $q = $injector.get('$q');

    deffered = $q.defer();
    spyOn($q, 'defer').andReturn(deffered);
    spyOn(deffered, 'resolve');
    $httpBackend.when('GET', 'mockEndPoint/testtest').respond(123);
}));

it("Get calls correct URL and resolves deffered on success", function () {
    $myService.get('testtest');
    $httpBackend.expect('GET', 'mockEndPoint/testtest');
    $httpBackend.flush(); //Added this
    expect(deffered.resolve).toHaveBeenCalledWith(123); //This assert fails
});
});

SERVICE CODE

myModule.factory('$myModule', ['$http', '$q', '$endpointService',     function ($http, $q, $endpointService) {
var apiPath = $endpointService.getApiEndpoint('MyApi');
this.get = function (id) {
    var deferred = $q.defer();
    var url = apiPath + '/' + id;
    console.log('GET: ' + url);
    $http.get(url).success(function (data) {
        deferred.resolve(data);
    }).error(function () {
        deferred.reject('error: could not load thingy with id: ' + id);
    });

    return deferred.promise;
};

}]);

So question one, i have 3 beforeEach blocks, can i merge some of them? I get all kinds of faults when i try to do it.

Also, how would i go about to assert that the resolved is called on success of the get? You see what I have tried with above, but it does not work.

Thx in advance.

Edit: Adding the flush seems to make some difference but not the expected result, so any help regarding promises and defer is really appreciated.

Edit2: Changed title to actually reflect what i had problems with.

//TWD

Upvotes: 3

Views: 1926

Answers (1)

Ye Liu
Ye Liu

Reputation: 8986

Remove $httpBackend.when from the last beforeEach block, and the test should look like the following:

it("Get calls correct URL and resolves deffered on success", function () {
    $httpBackend.expect('GET', 'mockEndPoint/testtest').respond(123);
    $myService.get('testtest');
    $httpBackend.flush();
    expect(deffered.resolve).toHaveBeenCalledWith(123);
});

$httpBackend.flush must be called to resolve the promise of $http.get. Only after that, your deferred object's resolve would be called.

Update:

I think it's a bad idea to spy on a vendor function to begin with, it's the last thing you want to do when you're unit testing your code. And, in your service code, it's not necessary to create a new deferred object, since $http.get will return a promise to you, so you can simplify the code like the following:

this.get = function (id) {
    var url = apiPath + '/' + id;
    console.log('GET: ' + url);

    return $http.get(url).success(function (data) {
        return data;
    }).error(function () {
        return 'error: could not load thingy with id: ' + id;
    });
};

Now, you can test it like this:

it("Get calls correct URL and resolves deffered on success", function () {
    $httpBackend.expect('GET', 'mockEndPoint/testtest').respond(123);

    $myService.get('testtest').then(function(data) {
        expect(data).toEqual(123);
    });

    $httpBackend.flush();
});

Upvotes: 4

Related Questions