22

I'm using Jasmine with Karma to test my app built on Angular.

I've to test a service that loads user data and I'm using $httpBackend to mock the responses. However, when I run the test, I got two errors:

Module:

'use strict';

app.service ('UserService', ['$resource', '$q', 'GITHUB_API_URL', function ($resource, $q, GITHUB_API_URL) {
  var userResource = $resource (GITHUB_API_URL + '/users/:user', {user: '@user'}) ,
      userModel = {};

  return {
    data: function () {
        return userModel;
    } ,
    populate: function (user) {
      var deferred = $q.defer () ,
          userRequest = userResource.get ({user: user});

      $q
          .when (userRequest.$promise)
          .then (function (data) {
              userModel = data;
              deferred.resolve (data);
          });

      return deferred.promise;
    }
  };
}]);

Test:

'use strict';

describe ('Service: UserService', function () {
    beforeEach (module ('myApp'));

    var $appInjector = angular.injector (['myApp']) ,
        UserService = $appInjector.get ('UserService') ,
        GITHUB_API_URL = $appInjector.get ('GITHUB_API_URL') ,
        GITHUB_USER = $appInjector.get ('GITHUB_USER') ,
        $httpBackend;

    beforeEach (inject (function ($injector) {
        $httpBackend = $injector.get ('$httpBackend');

        $httpBackend
            .when ('GET', GITHUB_API_URL + '/users/' + GITHUB_USER)
            .respond ({
                login: GITHUB_USER ,
                id: 618009
            });
    }));

    afterEach (function () {
        $httpBackend.verifyNoOutstandingExpectation ();
        $httpBackend.verifyNoOutstandingRequest ();
    });

    describe ('when populate method is called', function () {
        it ('should returns user data', function () {
            $httpBackend.expectGET (GITHUB_API_URL + '/users/' + GITHUB_USER);

            UserService.populate (GITHUB_USER);
            $httpBackend.flush ();
            expect(UserService.data ()).toEqual ({
                login: GITHUB_USER ,
                id: 618009
            });

        });
    });
});

Let's assume that GITHUB_API_URL is equal to 'https://api.github.com/' and GITHUB_USER is equal to 'wilk'.

I'm running this test with Karma-Jasmine 0.1.5 and AngularJS 1.2.6 (with Angular Mocks and Scenario 1.2.6).

What's wrong with this code?

Wilk
  • 7,873
  • 9
  • 46
  • 70

1 Answers1

48

Let's talk about each error separately:

Error: No pending request to flush!

That's happening because no request was made through $httpBackend, so there's nothing to flush. That's because you are instantiating UserService before $httpBackend and so Angular doesn't know it should use it instead of the real $http. If you check out the console you'll see that a real request is being sent.

Error: Unsatisfied requests: GET https://api.github.com/users/wilk

Same reason as the above. Since $httpBackend isn't being used by the service, the expectation you've created is never fulfilled.

Here's your spec refactored after considering all of the above:

describe ('Service: UserService', function () {
    var UserService,
        GITHUB_API_URL,
        GITHUB_USER,
        $httpBackend;

    beforeEach(function() {
      module('plunker');

      inject(function( _$httpBackend_, _UserService_, _GITHUB_API_URL_, _GITHUB_USER_) {
        $httpBackend = _$httpBackend_;
        UserService = _UserService_;
        GITHUB_API_URL = _GITHUB_API_URL_;
        GITHUB_USER = _GITHUB_USER_;
      });
    });

    afterEach (function () {
        $httpBackend.verifyNoOutstandingExpectation ();
        $httpBackend.verifyNoOutstandingRequest ();
    });

    describe ('when populate method is called', function () {
        it ('should returns user data', function () {
            $httpBackend
              .whenGET(GITHUB_API_URL + '/users/' + GITHUB_USER)
              .respond ({
                  login: GITHUB_USER,
                  id: 618009
              }); 

            UserService.populate(GITHUB_USER);
            $httpBackend.flush();

            expect(UserService.data().login).toBe(GITHUB_USER);
            expect(UserService.data().id).toBe(618009);
        });
    });
});

Plunker

Note: I've changed the way things were being injected a little bit, but the way you are doing is just fine, as long as you create $httpBackend before everything else.

Michael Benford
  • 14,044
  • 3
  • 60
  • 60
  • 2
    Woah! +1 for the great answer! Clear and detailed! Thanks for the explanation! However I didn't understand yet how 'inject' function works. How can inject such those renamed params? I mean, how does it know that _UserService_ it's a service of my module? – Wilk Feb 06 '14 at 07:14
  • 3
    @Wilk When you call `module('myApp')` Angular loads all services that module contains so the injector can use any of them. The underscores in the param names are just to prevent variable names from clashing. Angular just ignores them. You don't need to use them if your local variables have different names, though. More info on that [here](http://docs.angularjs.org/api/angular.mock.inject). – Michael Benford Feb 06 '14 at 16:55
  • Thanks so much, if I could +1 this again and again I would. I was pulling my hair out trying to figure out why my requests were unsatisfied. – Jonathan Palumbo Nov 27 '14 at 00:10
  • I wish I could vote this up again, months ago I was here for the first error, now I found it again for the second error answer lol. Thanks again. – jaredwilli May 18 '16 at 03:04
  • ummmm, can you elaborate "the expectation you've created is never fulfilled"? I am getting the `Error: Unsatisfied requests: PUT /api/v1/categories/by_key/zoom` error – Alexander Mills Dec 05 '17 at 04:42