Make $httpBackend ignore any requests made to server
Asked Answered
H

3

6

I have the following controller (notice that at instantiation time I make an explicit call to $scope.getNotifications()):

bla.controller("myctrl", [
    "$scope", "$http", "configs", function ($scope, $http, configs) {

        $scope.getNotifications = function () {
            $http.get("bla/blabla").success(function (data) {

            });
        };

        $scope.removeNotification = function (notification) {
            var index = $scope.allNotifications.indexOf(notification);
            $scope.allNotifications.splice(index, 1);
        };

        $scope.getNotifications();
    }
]);

Then I make some unit tests (notice that the controller gets instantiated in the before each):

describe("blaController", function () {

    var scope, $httpBackend;

    beforeEach(module('bla'));

    beforeEach(inject(function ($controller, $rootScope, _$httpBackend_) {
        scope = $rootScope.$new();
        $httpBackend = _$httpBackend_;
        $controller('blaCtrl', { $scope: scope });
    }));

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

 it("should get all notifications from server when instantiated", function () {
    //arrange
    $httpBackend.expectGET("api/v1/notifications").respond(200, {});
    $httpBackend.flush();

    //act - done implicitly when controller is instantiated  
});

it("should store all notifications from server on the client when success call to server", function () {
    //arrange
    $httpBackend.whenGET("api/v1/notifications").respond(200, [{ a: 1, b: 2 }, { c: 3, d: 4 }]);
    $httpBackend.flush();

    //act - done implicitly when controller is instantiated

    //assert
    expect(scope.allNotifications).toEqual([{ a: 1, b: 2 }, { c: 3, d: 4 }]);
});

Everything is fine until now. All tests pass. But when I add a new test (see bellow) that does not require any HTTP calls it fails because in the afterEach() it verifies for expecations but there are no expectations set in the removeNotification(). This is the error message from karma: PhantomJS 1.9.7 (Windows 8) notificationCenterController removeNotification should remove the given notification from th e list FAILED Error: Unexpected request: GET api/v1/notifications No more request expected

it("should remove the given notification from the list", function () {
            //arrange
            var targetObj = { a: 2 };
            scope.allNotifications = [{ a: 1 }, targetObj, { a: 3 }];

            //act
            scope.removeNotification(targetObj);

            //assert
            expect(scope.allNotifications).toEqual([{ a: 1 }, { a: 3 }]);
        });

Most of my test do have http calls so placing the verify in the afterEach makes sense. I was wondering what other option do I have to avoid copy pasting the afterEach body in N-1 tests. Is there a way to tell $httpBackend to ignore any calls?

Hashim answered 27/8, 2014 at 11:27 Comment(4)
In our project we moved the $http-calls to a factory and mocked that factory in the controller-tests. That way you can test the factory using $httpBackend and test controller logic without $httpBackend.Chalcis
Your controller has $scope.getNotifications(); on its last line so there are http calls being made. You're initiating the controller in a beforeEach, so it will be recreated for every single test and will therefore also execute the call to $http in every single test.Cyanogen
Yes you are right I updated the answer to be more clear. I know they are being made but not for the method in question that I want to test, hence I don't set any expectations in the test but the verifications still take place since being made after each test.Hashim
You could remove $httpBackend, and instead spy on $http.get and use that to do your assertions (or run your non-backend tests in a different describe block, and use spies there - then you get the best of both worlds).Wales
A
3

you can wrap your test in describe block like below.

describe("blaController", function () {

    var scope, $httpBackend;

    beforeEach(module('bla'));

    beforeEach(inject(function ($controller, $rootScope, _$httpBackend_) {
        scope = $rootScope.$new();
        $httpBackend = _$httpBackend_;
        $controller('blaCtrl', { $scope: scope });
    }));

    describe('test http calls', function() {

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

        it("should get all notifications from server when instantiated", function () {
            //arrange
            $httpBackend.expectGET("api/v1/notifications").respond(200, {});
            $httpBackend.flush();

            //act - done implicitly when controller is instantiated  
        });

        it("should store all notifications from server on the client when success call to server", function () {
        //arrange
            $httpBackend.whenGET("api/v1/notifications").respond(200, [{ a: 1, b: 2 }, { c: 3, d: 4 }]);
            $httpBackend.flush();

            //act - done implicitly when controller is instantiated

            //assert
            expect(scope.allNotifications).toEqual([{ a: 1, b: 2 }, { c: 3, d: 4 }]);
        });

    }); 

    describe('other tests', function(){
        it("should remove the given notification from the list", function () {
            //arrange
            var targetObj = { a: 2 };
            scope.allNotifications = [{ a: 1 }, targetObj, { a: 3 }];

            //act
            scope.removeNotification(targetObj);

            //assert
            expect(scope.allNotifications).toEqual([{ a: 1 }, { a: 3 }]);
        });
    });
});
Alkalify answered 7/6, 2016 at 9:48 Comment(0)
W
1

You can spy on $http.get in a separate suite, that should work (pseudo-code below).

 describe("backend", function() { // your code from before });

 describe("non-backend", function () {

    var scope, $http;

    beforeEach(module('bla'));

    beforeEach(inject(function ($controller, $rootScope, _$http_) {
        scope = $rootScope.$new();
        $http = _$http_;
        spyOn($http, 'get').and.callFake(function() {
            return { some: 'data' };
        });
        $controller('blaCtrl', { $scope: scope, $http: $http });
    }));

    it("should remove the given notification from the list", function () {
        //arrange
        var targetObj = { a: 2 };
        scope.allNotifications = [{ a: 1 }, targetObj, { a: 3 }];

        //act
        scope.removeNotification(targetObj);

        //assert
        expect(scope.allNotifications).toEqual([{ a: 1 }, { a: 3 }]);
    });
});
Wales answered 7/6, 2016 at 9:8 Comment(0)
C
1

Since you are making the http request in the constructor, and the constructor is being run every time you run a test, then the httpBackend should be prepared to respond to this request every time.

So in your beforeEach block you shoud be setting:

notificationsRequest = $httpBackend.expectGET("api/v1/notifications").respond(200, {});

In your test cases, you can alter this response, by calling the respond method again, which overwrites the response set like this:

it("should store all notifications from server on the client when success call to server", function () {
    //arrange
    notificationsRequest.respond(200, [{ a: 1, b: 2 }, { c: 3, d: 4 }]);
    $httpBackend.flush();

    //act - done implicitly when controller is instantiated

    //assert
    expect(scope.allNotifications).toEqual([{ a: 1, b: 2 }, { c: 3, d: 4 }]);
});

Now, all you need to do, is make sure that you are flushing the responses after each test. Given, this may not be convenient, but it must be done if you want to verifyNoOutstandingRequest since by definition of your constructor, there will be an outstanding request. I would suggest you do this by structuring your tests into nested suites, and for the ones that are not testing the retrieval of notifications, flushing the request in their own beforeEach block.

Claudianus answered 7/6, 2016 at 9:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.