DarkLazar
DarkLazar

Reputation: 83

Unit Testing angular $httpBackend service

I've got right now a project were we need to have the backend server mocked for the time being and we are using $httpBackend on the application .run feature. I need to unit test this service that contains the $httpBackend as we will be having a vast amount of mocked calls to the server we will be covering. So right now this is what I have. As a preface to my question the current setup works when I call mockDataService.getWorkflowTask from a controller on a simple page.

My Server replacement service:

angular.module('app').run(function ($httpBackend, $resource, FakeBackendService) {
    // TODO: add all necessary http intercepts.  
    $httpBackend.whenGET('JSON file').respond(function (method, url, data) {
        var request = new XMLHttpRequest();
        request.open('GET', url, false);
        request.send(null);
        return [request.status, request.response, {}];
    });
    $httpBackend.whenGET(/.*/).respond(function (method, url, data) {

        return [200, FakeBackendService.getWorkflowTasks(), {}];
    });
});

Here is the service for FakeBackendService:

(function () {
    'use strict';

    var injectParams = [];

    function service(lodash) {
        var vm = this;

        var ret = {
            getWorkflowTasks: getWorkflowTasks
        };

        function getWorkflowTasks() {
            if (vm.workflowtasks.length < 1) {
                vm.workflowtasks = loadWorkflowTasks("Some JSON file");
            }
            return vm.workflowtasks;
        };

        function loadWorkflowTasks(file) {
            var workflowTasks = [];
            var request = new XMLHttpRequest();
            request.open("GET", file, false);
            request.send(null);

            if (request.status == 200) {
                workflowTasks = angular.fromJson(request.response);
            }
            return workflowTasks;
        };

        function init() {
            vm.workflowtasks = [];
        }

        init();

        return ret;
    }

    service.$inject = injectParams;
    angular.module('mock.FakeBackendService', []).service('FakeBackendService', service);
})();

So that is currently the backend server replacement mock. The following is my data handling service which contains the call to $http.get(blah blah blah).

(function () {
    'use strict';

    var injectParams = ['$http', '$q', 'mockConfigService', '$httpBackend'];

    function factory($http, $q, configService, $httpBackend) {
        var vm = this;

        var factory = {
            getWorkflowTask: getWorkflowTask
        };

        function getWorkflowTask(str) {
            return getResource(str);
        }

        function init() {
            // Get the URL we will be using to get data from
            vm.dataServiceURL = configService.getDataServiceURL();
        }

        function getResource(baseResource) {
            var resource = vm.dataServiceURL + baseResource;

            return $http.get(resource).then(function (response) {
                if (typeof response.data == 'object') {
                    // Got valid response
                    return $q.resolve(response.data);
                }
                else {
                    // Invalid response
                    return $q.reject(response.data);
                }
            }, function (response) {
                // Something went wrong
                return $q.reject(response.data);
            });
        }
        init();

        return factory;
    };

    factory.$inject = injectParams;

    angular.module('mock.dataService', []).factory('mockDataService', factory);
}());

Now for the Jasmine-Karma Unit test.

describe("HTTP Backend Mock testing", function () {

    beforeEach(angular.mock.module("app"));
    beforeEach(angular.mock.module("mock.FakeBackendService"));
    beforeEach(angular.mock.module("mock.configService"));
    beforeEach(angular.mock.module("mock.dataService"));

    it("Get the workflow task", angular.mock.inject(function (mockDataService) {
        var valid = "";

        var promise = mockDataService.getWorkflowTask('http://localhost/foo');

        promise.then(function (response) {
            valid = "Success";
        }, function (response) {
            valid = "Failure";
        });

        expect(valid).toBe("Success");
    }));
});

Now to the question. So, I'll start by saying I'm new to the AngularJS world and even more so to Jasmine. Anyways, when I debug the unit test I find that the promise's status is still 0 and I always get expected '' to be 'Success' telling my I never resolve (hopefully I'm using the right lingo) the promise from the $http service in mockDataService. I've tried playing around with it some and tried to see if anyone has done this kind of a thing before. I found plenty of examples where the $httpBackend is mocked in the test but none like what I'm attempting. Any ideas or suggestions would be great. Thanks.

EDIT got a slightly working solution

So I decided that I'd by pass the run() service and just do the same response in the expectGET().respond().

describe("HTTP Backend Mock testing", function () {
    beforeEach(angular.mock.module("app"));
    beforeEach(angular.mock.module("mock.FakeBackendService"));
    beforeEach(angular.mock.module("mock.configService"));
    beforeEach(angular.mock.module("mock.dataService"));

    it("Get the workflow task", angular.mock.inject(function (mockDataService, $httpBackend, FakeBackendService) {
        var valid = "";
        $httpBackend.expectGET('http://server:80/api/foo').respond(200, FakeBackendService.getWorkflowTasks());
        var promise = mockDataService.getWorkflowTask('foo');


        promise.then(function (response) {
            valid = "Success";
        }, function (response) {
            valid = "Failure";
        });
        $httpBackend.flush();

        expect(valid).toBe("Success");
    }));
});

This sort of solves my testing problem with the run() as the goal was to verify 1) That the regex matching call the correct FakeBackendService and 2) That FakeBackendService returns correct file and actually loads it. I think I can do that by mimicking the same regex in the expectGET. However, I'll leave open for a bit to see if anyone knows how to get the run() to work.

Upvotes: 0

Views: 1398

Answers (2)

Austin
Austin

Reputation: 1291

The promise is not going to resolve unless you force it to do so before the test ends. Here is one such way to do it:

    $httpBackend.expectGET(......).respond(200, 'abc');

    var promise = mockDataService.getWorkflowTask('http://localhost/foo');

    promise.then(function (response) {
        valid = "Success";
    }, function (response) {
        valid = "Failure";
    });

    //new code here
    $httpBackend.flush();

    expect(valid).toBe("Success");

This will force the promise to resolve and your test should pass. You'll also need to inject the $httpBackend service into the test.

Upvotes: 1

Krzysztof Safjanowski
Krzysztof Safjanowski

Reputation: 7438

angular.module('mock.dataService', [])
  .service('mockDataService', function($http) {
    this.getWorkflowTask = function(url) {
      return $http.get(url)
    }
  })

describe('HTTP Backend Mock testing', function() {
  var $httpBackend
  beforeEach(angular.mock.module("mock.dataService"));
  beforeEach(inject(function(_$httpBackend_) {
    $httpBackend = _$httpBackend_
  }))

  it("Get the workflow task", angular.mock.inject(function(mockDataService) {
    $httpBackend.expectGET('http://localhost/foo').respond(200);

    var promise = mockDataService.getWorkflowTask('http://localhost/foo');

    promise.then(function(response) {
      valid = "Success";
    }, function(response) {
      valid = "Failure";
    });

    $httpBackend.flush();

    expect(valid).toBe("Success");
  }));
})
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-mocks.js"></script>

Upvotes: 0

Related Questions