7

I am trying to unit test my AngularJS application using Karma and Jasmine. I want to mock the $http service. For that, I am using the $httpBackend method. Below is my service that I want to test:

angular.module('MyModule').factory('MyService', function($http, $log, $parse, $q, $timeout, $filter, MyOtherService1, MyOtherService2){
var service = {};
   service.getSomething = function(id){
     return $http.get('/somePath/subpath/' + id);
   }
});

My unit test for this service is:

describe("myTest", function(){
    var myService, $httpBackend, scope, mockMyOtherService1, mockMyOtherService2;

    var myResponse =
    {
        foo:'bar'
    };

    beforeEach(module("MyModule"));

    beforeEach(inject(function(_MyService_, $injector){

        $httpBackend = $injector.get('$httpBackend');
        myService = _MyService_;
        scope = $injector.get('$rootScope').$new();
        mockMyOtherService1 = $injector.get('MyOtherService1');
        mockMyOtherService2 = $injector.get('MyOtherService2');

    }));

    beforeEach(function(){
        //To bypass dependent requests
        $httpBackend.whenGET(/\.html$/).respond(200,'');
    });

    //If I uncomment the below afterEach block, the same error is shown at next line.
    /*afterEach(function() {
         $httpBackend.verifyNoOutstandingExpectation();
         $httpBackend.verifyNoOutstandingRequest();
     });*/

    //This test passes successfully
    it("should check if service is instantiated", function () {
        expect(myService).toBeDefined();
    });

    //This test passes successfully
    it("should expect dependencies to be instantiated", function(){
        expect($httpBackend).toBeDefined();
    });

    //The problem is in this test
    it("should get the getSomething with the provided ID", function() {
        $httpBackend.whenGET('/somePath/subpath/my_123').respond(200,myResponse);            
        var deferredResponse = myService.getSomething('my_123');

        //The error is shown in next line.
        $httpBackend.flush();      

        //If I comment the $httpBackend.flush(), in the next line, the $$state in deferredResponse shows that the Object that I responded with is not set i.e. it does not matches the 'myResponse'.
        expect(deferredResponse).toEqual(myResponse);

    });
});

This is an emergency problem and I need help regarding the same as soon as possible. I will be very grateful for your answer.

Jagrut
  • 922
  • 7
  • 21

3 Answers3

2

The problem was I needed to inject $location in my spec files even though they are not injected in the services. After injection, all worked well! Hope this helps someone who gets stuck in the same situation.

Jagrut
  • 922
  • 7
  • 21
  • @TusharRaj What is the exact problem you are facing? Please point me to your question on stackoverflow – Jagrut Mar 08 '16 at 14:32
1

You will get a promise from your service. So change your test code to:

//The problem is in this test
it("should get the getSomething with the provided ID", function (done) {
  $httpBackend.expectGET('/somePath/subpath/my_123').respond(200,myResponse);
  var deferredResponse = myService.getSomething('my_123');

  deferredResponse.then(function (value) {
    expect(value.data).toEqual(myResponse);
  }).finally(done);

  $httpBackend.flush();
});
ogugger
  • 122
  • 6
  • I tried your code but it gives the same error because the error is at the line $httpBackend.flush(). It does not execute after that line itself. If I comment out the $httpBackend.flush() line, then it works. But I need $httpBackend.flush() and afterEach() block. – Jagrut Nov 23 '15 at 13:37
  • I updated the answer, moved the flush() call to the end. It now works on my machine. Can you try again with the updated code? – ogugger Nov 23 '15 at 14:18
  • Still the same error! I think I am overlooking something but I am not able to find it out. Even if I do not write any it() block, afterEach() should work but it is also not working! So I had to comment it out and proceed further. But even then I am stuck at $httpBackend.flush()! – Jagrut Nov 23 '15 at 14:31
  • The same error mentioned in the title of the question. If I comment out the $httpBackend.flush() line from the code provided by @ogugger then I get _Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL._. I am stuck with this problem since 3 days and I have checked out many resources on Google and Stackoverflow too but none of them worked. Maybe it is some logical error as I am pretty new to this. I request you to please look at the service that I created and if there is any problem in that. – Jagrut Nov 23 '15 at 16:16
  • Try removing the other services (MyOtherService1, MyOtherService2) so you only have the code that you posted and then do my changes. Does it still behave the same way? – ogugger Nov 24 '15 at 06:25
  • I checked again the same code by placing the console.log inside the deferredResponse.then() function. It does NOT get executed! – Jagrut Nov 24 '15 at 13:24
1

I've recently had this problem when updating a project from Angular 1.2 to 1.4. The test code looked something like:

it('should call /something', function(){
    httpBackend.expectGET('/something').respond(200);
    scope.doSomething();
    httpBackend.flush();
});

The error was the infdig past 10 iterations. It was caused by invoking the .flush() method. I figured out this is seemingly because there were no pending promises created within doSomething().

Once I added a promise somewhere within doSomething() or inner methods, the infdig problem went away.

I suspect - and this is 100% speculation so don't let it influence your development - this is because httpBackend does some trickery to wait for promises, which maybe involves digesting repeatedly until there's a change. Since there's no promises, there's no changes - infinite digest.

oooyaya
  • 1,773
  • 1
  • 15
  • 39