0

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.

1 Answer 1

1

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();
            });
        });
    });

}()); 
Sign up to request clarification or add additional context in comments.

11 Comments

I tried this, look at my update on how I implemented it and the errors being thrown
I was able to get rid of the unexpected call error. now im getting TypeError: 'undefined' is not a function (evaluating 'controller.controller()')
Stop calling $scope.$appy() you don't need to - that'll get rid of one error. Why are you calling controller.controller() - i know i put it in my code but that was just an example - albeit badly named. See my edit
TypeError: 'undefined' is not an object (evaluating 'controller.addRepository(givenURL).then') Error: Unflushed requests: 1
Hmm - give me a minute to emulate this - it's starting to annoy me
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.