CoffeePeddlerIntern
CoffeePeddlerIntern

Reputation: 679

AngularJS Testing and $http

So Im trying to figure out how to write unit tests for my angular controller. I am using karma as my runner. I was able to write 1 successful test but every time I try to write another test it yells at me about unexpected calls and such.

Here is my controller im trying to test.

(function (angular) {
'use strict';
var ngModule = angular.module('myApp.dashboardCtrl', []);
ngModule.controller('dashboardCtrl', function ($scope, $http) {

    //"Global Variables"
    var vm = this;
    vm.success = false;
    vm.repos = [];

    //"Global Functions"
    vm.addRepository = addRepository;
    vm.listRepos = listRepos;

    //Anything that needs to be instantiated on page load goes in the init
    function init() {
        listRepos();
    }
    init();

    // Add a repository
    function addRepository(repoUrl) {
        $http.post("/api/repo/" + encodeURIComponent(repoUrl)).then(function (){
            vm.success = true;
            vm.addedRepo = vm.repoUrl;
            vm.repoUrl = '';
            listRepos();
        });
    }
    //Lists all repos
    function listRepos() {
        $http.get('/api/repo').then( function (response){
            vm.repos = response.data;

        });
    }
});
}(window.angular));

So I have a test written for listRepos(). It goes as follows

describe('dashboardCtrl', function() {
var scope, httpBackend, createController;
// Set up the module
beforeEach(module('myApp'));

beforeEach(inject(function($rootScope, $httpBackend, $controller) {
    httpBackend = $httpBackend;
    scope = $rootScope.$new();
    createController = function() {
        return $controller('dashboardCtrl', {
            '$scope': scope
        });
    };
}));

afterEach(function() {
    httpBackend.verifyNoOutstandingExpectation();
    httpBackend.verifyNoOutstandingRequest();
});

it('should call listRepos and return all repos from the database', function() {
    var controller = createController();
    var expectedResponse = [{id: 12345, url: "https://github.com/myuser/myrepo.git"}];

    httpBackend.expect('GET', '/api/repo')
        .respond(expectedResponse);
    httpBackend.flush();

    scope.$apply(function() {
        scope.listRepos;
    });

    expect(controller.repos).toEqual(expectedResponse);
});

This works and the test passes. Now my problem is I want to write another test to test the other function that calls a new api endpoint.

This is the test im trying to write for addRepository.

 it('should addRepository to the database', function() {
        var controller = createController();
        var givenURL = "https://github.com/myuser/myURLtoMyRepo.git";

        httpBackend.expect('POST', '/api/repo/' + encodeURIComponent(givenURL)).respond('success');
        httpBackend.flush();

        scope.$apply(function() {
            scope.addRepository(givenURL);
        });

        expect(controller.success).toBe(true);
        expect(controller.listRepos).toHaveBeenCalled();
    });

The error I get when I add this test to the spec is:

Error: Unexpected request: GET /api/repo
Expected POST /api/repo/https%3A%2F%2Fgithub.com%2Fmyuser%2FmyURLtoMyRepo.git
    at $httpBackend 
Error: [$rootScope:inprog] $digest already in progress
http://errors.angularjs.org/1.4.8/$rootScope/inprog?p0=%24digest

The example I am working with is this one here

Any suggestions or tips is greatly appreciated!

UPDATE:

So changed my function to return the promise from the $http.post,

I rewrote my 2nd test and also wrapped my first test in a describe block describing the function its trying to test.

With the following:

describe('addRepository', function () {

    it('should addRepository to the database', function () {
        var controller = createController();
        var givenURL = "https://github.com/myuser/myURLtoMyRepo.git";

        httpBackend.expect('POST', '/api/repo/' + encodeURIComponent(givenURL)).respond('success');

        scope.$apply(function () {
            scope.addRepository(givenURL);
        });
        httpBackend.flush();

        expect(controller.success).toBe(true);
    });
    it('should call listRepos', function() {
        var controller = createController();
        httpBackend.expect('GET', '/api/repo').respond('success');

        controller.controller().then(function (result) {
            expect(controller.listRepos).toHaveBeenCalled();

        });
        httpBackend.flush();
    });
});

I still get the error:

Error: Unexpected request: GET /api/repo
Expected POST /api/repo/https%3A%2F%2Fgithub.com%2Fmyuser%2FmyURLtoMyRepo.git
    at $httpBackend 
Error: [$rootScope:inprog] $digest already in progress

but also

TypeError: 'undefined' is not a function (evaluating 'controller.controller()') 
Error: Unflushed requests: 1

which shows 2 tests failed.

Upvotes: 0

Views: 126

Answers (1)

Katana24
Katana24

Reputation: 8959

The flush should come after the call to the function. I'd also change the function to return the promise from the $http.post:

    // Add a repository
    function addRepository(repoUrl) {
        return $http.post("/api/repo/" + encodeURIComponent(repoUrl)).then(function (){
            vm.success = true;
            vm.addedRepo = vm.repoUrl;
            vm.repoUrl = '';
            listRepos();
        });
    }

And then in the test you can call it and test the success part:

EDIT

I changed the controller.controller() to what you have.

it('should call listRepos', function() {
    // Your setup
    ctrl.addRepository().then(function(result) {
        expect(ctrl.listRepos).toHaveBeenCalled();
    });
});

EDIT 2

I emulated as best i could your code and the tests I write for the code:

(function () {
  'use strict';

  angular
    .module('myApp')
    .controller('DashboardController',DashboardController);

    DashboardController.$inject = ['$http'];

    function DashboardController($http) {

      var vm = this;
      vm.success = false;
      vm.repos = [];

      vm.addRepository = addRepository;
      vm.listRepos = listRepos;

      init();

      // Anything that needs to be instantiated on page load goes in the init
      function init() {
        vm.listRepos();
      } 

      // Add a repository
      function addRepository(repoUrl) {
        return $http.post('http://jsonplaceholder.typicode.com/posts/1.json').then(function (){
          vm.success = true;
          vm.addedRepo = vm.repoUrl;
          vm.repoUrl = '';
          vm.listRepos();
        });
      }
      // Lists all repos
      function listRepos() {
        return $http.get('http://jsonplaceholder.typicode.com/posts/1').then( function (response){
          vm.repos = response.data;
        });
      }
    };
}());

Here I'm using an online JSONPlaceholder API to simulate HTTP calls as I, obviously, can't hit what you're pointing at. And for the test (which all pass):

(function() {
    'use strict';

    fdescribe('DashBoardController', function() {
        var $rootScope, scope, ctrl, $httpBackend;
        beforeEach(module('myApp'));

        beforeEach(inject(function(_$rootScope_, _$httpBackend_,$controller) {
            $rootScope      = _$rootScope_; 
            scope           = $rootScope.$new();
            $httpBackend    =_$httpBackend_;

            ctrl = $controller('DashBoardController',{
                $scope: scope
            });
        }));

        beforeEach(function() {
            // Setup spies
            spyOn(ctrl,'listRepos');
        });

        describe('controller', function() {
            it('should be defined', function() {
                expect(ctrl).toBeDefined();
            });
            it('should initialize variables', function() {
                expect(ctrl.success).toBe(false);
                expect(ctrl.repos.length).toBe(0);
            });
        });

        describe('init', function() {

            it('should call listRepos', function() {
                $httpBackend.expectGET('http://jsonplaceholder.typicode.com/posts/1')
                    .respond({success: '202'});

                $httpBackend.expectPOST('http://jsonplaceholder.typicode.com/posts/1.json')
                    .respond({success: '202'});

                ctrl.addRepository().then(function(result) {
                    expect(ctrl.success).toBe(true);
                    expect(ctrl.repoUrl).toBe('');
                    expect(ctrl.listRepos).toHaveBeenCalled();
                });

                $httpBackend.flush();
            });
        });
    });

}()); 

Upvotes: 1

Related Questions