Search code examples
angularjsunit-testingjasmineintegration-testingangularjs-service

Testing the API responses format returned by angular service with Jasmine


The majority of the problems occur when the format of the interaction between me and the API changes. I want to test my angular service which talks with the API. How can I inject my angular service into test and get proper results if this service uses $http? Should I use jasmine, the tool for unit testing for this type of integration tests?

In this example I'm testing the OntologyService which uses $http and returns a promise, and the test looks like this:

describe('Service: OntologyService', function () {
    var OntologyService, $scope;
    beforeEach(function () {
        module('oneClickRegistrationApp');
        inject(function ($injector) {
            OntologyService = $injector.get('OntologyService');
            $scope = $injector.get('$rootScope').$new();
        });
    });

    it('should return the object of ontologies', inject(function () {
        var ontoServerApiUrl = 'https://myurl.com/api/ksearch/ontologies/';
        OntologyService.getAllOntologies(ontoServerApiUrl).then(function (ontologies) {
            expect(ontologies).toBeNonEmptyObject();
            expect(ontologies["licenses"]).toHaveArrayOfObjects();
            expect(ontologies["species"]).toHaveArrayOfObjects();
            expect(ontologies["measurement_methods"].length).toBeGreaterThan(10);
        });
        $scope.$digest();
    }));
});

I'm getting the following error message:

PhantomJS 1.9.8 (Mac OS X 0.0.0) Service: OntologyService should return the object of ontologies FAILED
    Error: Unexpected request: GET https://myurl.com/api/ksearch/ontologies/hbp_data_modality_ontology?size=10000
    No more request expected
        at $httpBackend (/Users/katkov/WebstormProjects/one-click/bower_components/angular-mocks/angular-mocks.js:1323)
        at sendReq (/Users/katkov/WebstormProjects/one-click/bower_components/angular/angular.js:10761)
        at /Users/katkov/WebstormProjects/one-click/bower_components/angular/angular.js:10470
        at processQueue (/Users/katkov/WebstormProjects/one-click/bower_components/angular/angular.js:14991)
        at /Users/katkov/WebstormProjects/one-click/bower_components/angular/angular.js:15007
        at /Users/katkov/WebstormProjects/one-click/bower_components/angular/angular.js:16251
        at /Users/katkov/WebstormProjects/one-click/bower_components/angular/angular.js:16069
        at /Users/katkov/WebstormProjects/one-click/test/spec/services/realontologyservice.js:32
        at invoke (/Users/katkov/WebstormProjects/one-click/bower_components/angular/angular.js:4535)
        at workFn (/Users/katkov/WebstormProjects/one-click/bower_components/angular-mocks/angular-mocks.js:2517)
    undefined
PhantomJS 1.9.8 (Mac OS X 0.0.0): Executed 9 of 9 (1 FAILED) (0.016 secs / 0.158 secs)

Solution

  • Error: Unexpected request: GET https://

    This blog post gets you covered: http://www.bradoncode.com/blog/2015/06/16/unit-test-http-ngmock-passthrough/

    ...$httpBackend service requires us to mock all HTTP requests used in the code under test...

    ...it would be nice to make a real HTTP call so that I can experiment, get some example JSON etc...

    ...ngMock does include ngMockE2E, which allows us to create fake backend HTTP calls, but we can only use this in the full application i.e. via the browser and not from unit tests...

    Here is how to make real http request:

    describe('real http tests', function() {
    
      beforeEach(angular.mock.http.init);
      afterEach(angular.mock.http.reset);
    
      beforeEach(inject(function(_$controller_, _$httpBackend_) {
        $controller = _$controller_;
        $scope = {};
        $httpBackend = _$httpBackend_;
    
        // Note that this HTTP backend is ngMockE2E's, and will make a real HTTP request
        $httpBackend.whenGET('http://www.omdbapi.com/?s=terminator').passThrough();
      }));
    
      it('should load default movies (with real http request)', function (done) {
        var moviesController = $controller('MovieController', { $scope: $scope });
    
        setTimeout(function() {
          expect($scope.movies).not.toEqual([]);
          done();
        }, 1000);
    
      });
    
    });