0

For specific purposes, I had to write a factory method that returns a $timeout promise that, inside of it, returns a $http.get promise.

I want to test if a call to my factory method will call the $http.get with the correct mocked URL (mocked-get-path).

Here is my factory code:

(function() {
  'use strict';

  angular.module('MyApp', []);

  angular.module('MyApp')
    .constant("ROUTES", {
      get: "real-get-path"
    });

  angular.module('MyApp')
    .factory('MyFactory', ['$http', '$timeout', 'ROUTES', MyFactory]);

  function MyFactory($http, $timeout, ROUTES) {
    return {
      myGet: function(id) {
        var random = Math.random() * 1000;
        return $timeout(function () {
            return $http.get(ROUTES.get, {
              params: { id: id }
            })
              .then(function() {
                return response.data;
              });
        }, random);
      }
    };
  }
})();

And my test specification:

describe('simple factory test', function() {
  var $http, $timeout, scope, MyFactory;

  var ROUTES = {
      get: 'mocked-get-path'
  };

  beforeEach(module('MyApp', function ($provide) {
      $provide.constant('ROUTES', ROUTES);
  }));

  beforeEach(module('MyApp'));

  beforeEach(inject(function(_$rootScope_, _$http_, _$timeout_, _MyFactory_) {
    scope = _$rootScope_.$new();
    $http = _$http_;
    $timeout = _$timeout_;
    MyFactory = _MyFactory_;
  }));

  it('should use ROUTES.get on method myGet', function(done) {
    spyOn(Math, "random").and.returnValue(0.01);

    MyFactory.myGet('elem1')
      .then(function(res) {
        expect($http.get).toHaveBeenCalledWith(ROUTES.get);
        done();
      });
    $timeout.flush();
  });
});

You can see that I tried to write a expect for the $http.get inside the then, but it didn't work.

I receive this error from Jasmine:

Error: Unexpected request: GET mocked-get-path?id=elem1

I made a Plunker: https://plnkr.co/edit/Ia6Q6GvKZOkNU2B8GrO1

What am I doing wrong?

2 Answers 2

1

When testing $http you are going to want to use $httpBackend.when

In your case:

it('should use ROUTES.get on method myGet', function(done) {
  spyOn(Math, "random").and.returnValue(0.01);
  spyOn($http, "get").and.callThrough();

  MyFactory.myGet('elem1')
    .then(function(res) {
      expect($http.get).toHaveBeenCalledWith(ROUTES.get, {params: {id: 'elem1'}});
      done();
    });

  $httpBackend
    .when('GET', "mocked-get-path?id=elem1")
    .respond(200, { foo: 'bar' });

  $timeout.flush(100);

  $timeout.verifyNoPendingTasks();
  $httpBackend.flush();

});

This will cause $httpBackend to complete your request successfully allowing your .then with the expect to execute.

Plunkr showing solution

ngmock fundamentals

I hope this helps! Have a nice day!

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for your answer, but I've tried something like this and didn't work. Please check this modification I wrote with your suggestion: plnkr.co/edit/RwD3y9OmxSEywUhq2mUh
I've solved it. My solution was missing $httpBackend.flush() after $timeout.flush()
Thank you so much! Here is the modification: plnkr.co/edit/YvF3Lem8pYRmm0SpCke2
1

One approach is to add expected calls to the httpBackend like this:

var  $httpBackend;
beforeEach(function () {
        inject(function ( _$httpBackend_ ) {
            $httpBackend = _$httpBackend_;
        }

....

it('....', function(){
    //before the rest of the test
    $httpBackend.expectGET('url').respond("OK");

});

https://docs.angularjs.org/api/ngMock/service/$httpBackend

So, your code will look like this:

describe('simple directive', function() {
  var $http, $timeout, scope, MyFactory, $httpBackend;

  var ROUTES = {
      get: 'mocked-get-path'
  };

  beforeEach(module('MyApp', function ($provide) {
      $provide.constant('ROUTES', ROUTES);
  }));

  beforeEach(module('MyApp'));

  beforeEach(inject(function(_$rootScope_, _$http_, _$timeout_, _MyFactory_, _$httpBackend_) {
    $httpBackend = _$httpBackend_;
    scope = _$rootScope_.$new();
    $http = _$http_;
    $timeout = _$timeout_;
    MyFactory = _MyFactory_;
  }));

  it('should use ROUTES.get on method myGet', function(done) {
    spyOn(Math, "random").and.returnValue(0.01);
    $httpBackend.expectGET("mocked-get-path?id=elem1").respond("OK");
    MyFactory.myGet('elem1')
      .then(function(res) {
        expect($http.get).toHaveBeenCalledWith(ROUTES.get);
        done();
      });
    $timeout.flush();
  });
});

It will get you passed the unexpected GET error. But then there is another issue about not getting the call within the timeout period.

And, providing that now you are expecting a correct call at the $httpBackend, you can simplify your test case by removing the async nature of it like this:

(remove the paremeter done, and simplify the test code)

it('should use ROUTES.get on method myGet', function() {
    spyOn(Math, "random").and.returnValue(0.01);
    $httpBackend.expectGET("mocked-get-path?id=elem1").respond("OK");
    MyFactory.myGet('elem1');
    $timeout.flush();
  });

7 Comments

Thanks for your answer, I tried what you suggested but it didn't work. Please check this modification I wrote with your suggestion: plnkr.co/edit/b0hyazxaC5P3qKpLjqLm
I've attached the complete code that fixes the unexpected GET problem.
Thanks again. Now the error is other: "Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL." Here is the modification: plnkr.co/edit/RwD3y9OmxSEywUhq2mUh
see my last edit. your test conditions are now checked by expectations on httpBackend, so it can be done by sync calls.
Thanks again, but why the Jasmine result tells "SPEC HAS NO EXPECTATIONS should use ROUTES.get on method myGet"? Updated plunker: plnkr.co/edit/RwD3y9OmxSEywUhq2mUh
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.