4

I am unittesting my AngularJS app with Karma and stubbing the backend with $HttpBackend. Somehow, the flush() method does not seem to resolve all my requests as some of the variables in the controller still remain undefined. However, if I add a timeout before resolving my expectation it works fine!

My Controller:

    feedbackApp.controller('CompetenceCtrl', [ '$scope', '$location', 'Restangular', function CompetenceCtrl($scope, $location, Restangular) {

        $scope.compId = null;
        $scope.index = null;

        Restangular.one('questionnaires',1).get().then(function (q) {
            $scope.questionnaire = q;

            angular.forEach($scope.questionnaire.competences, function (value, key) {
                var compTemp = new models.Competence(value);
                if (!compTemp.finished() && $scope.compId === null) {
                    $scope.compId = compTemp.id;
                    $scope.index = key;
                }
            });
            getCompetence($scope.compId);

        });

        function getCompetence(compId) {
            Restangular.one('questionnaires',1).one('competences', compId).get().then(function (c) {
                $scope.competence = c;
            });
        }
    }]);

My specs:

'use strict';

describe('Controller: CompetenceCtrl', function () {
    //load the controller's module
    beforeEach(module('360FeedbackApp', 'mockQuestionnaire', 'mockCompetences'));

    var $httpBackend,
        $scope,
        $location,
        createController;

    beforeEach(inject(function ($injector, _Restangular_,defaultQuestionnaire, defaultCompetences) {
        // Set up the mock http service responses
        $httpBackend = $injector.get('$httpBackend');
        // backend definition common for all tests
        $httpBackend.whenGET(apiUrl + '/questionnaires/1').respond(defaultQuestionnaire);

        $httpBackend.whenGET(apiUrl + '/questionnaires/1/competences/1').respond(defaultCompetences.competences[0]);
        $httpBackend.whenGET(apiUrl + '/questionnaires/1/competences/2').respond(defaultCompetences.competences[1]);
        $httpBackend.whenGET(apiUrl + '/questionnaires/1/competences/3').respond(defaultCompetences.competences[2]);
        $httpBackend.whenGET(apiUrl + '/questionnaires/1/competences/4').respond(defaultCompetences.competences[3]);
        $httpBackend.whenGET(apiUrl + '/questionnaires/1/competences/5').respond(defaultCompetences.competences[4]);

        $httpBackend.whenPUT(apiUrl + '/questionnaires/1/competences/1').respond(200);
        $httpBackend.whenPUT(apiUrl + '/questionnaires/1/competences/2').respond(200);
        $httpBackend.whenPUT(apiUrl + '/questionnaires/1/competences/3').respond(200);
        $httpBackend.whenPUT(apiUrl + '/questionnaires/1/competences/4').respond(200);
        $httpBackend.whenPUT(apiUrl + '/questionnaires/1/competences/5').respond(200);

        // Get hold of a scope (i.e. the root scope)
        $scope = $injector.get('$rootScope');
        // and the location
        $location = $injector.get('$location');
        // The $controller service is used to create instances of controllers
        var $controller = $injector.get('$controller');

        createController = function () {
            return $controller('CompetenceCtrl', {'$scope': $scope, '$location': $location, 'Restangular': _Restangular_ });
        };
    }));

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

    it('should set the first competence', function () {
        $httpBackend.expectGET(apiUrl + '/questionnaires/1/competences/1');
        createController();
        $httpBackend.flush();
        //DO NOT UNDERSTAND WHY I NEED THIS TIMEOUT! 
        //THE TEST FAILS WITH 'undefined' IF I DONT USE IT!
        setTimeout(function() {
        expect($scope.competence).toBeDefined();
        }, 5000);
    });

Any help is greatly appreciated!

M Jacobs
  • 251
  • 1
  • 3
  • 9
  • 1
    Hm. Try calling `$scope.$apply` and then check `$scope.competence` value. The problem might be in in $q promises (they are not resolved unless you call `$scope.$apply`) – jusio Sep 26 '13 at 15:04
  • Thx. $scope.$apply() didn't work out, but $scope.digest() did. – M Jacobs Oct 08 '13 at 14:00
  • Additionally, the specific scenario above with the timeout passed the test because the time-out made expect() to only fire AFTER all unittest were already executed. So failing to meet the expectation did not fail any test.... :S – M Jacobs Oct 08 '13 at 14:02

1 Answers1

1

When you have promises that need to be resolved (indicated by the .then() on your Restangular call) you need to call $scope.$digest() after your $httpBackend.flush() to resolve them. This also sounds like perhaps your Restangular call is hitting the actual server instead of a mock, which would be what is causing you to need the timeout.

MBielski
  • 6,628
  • 3
  • 32
  • 43
  • actually I had to put the $scope.$digest() before the $httpBackend.flush()..... not exactly sure why. – M Jacobs Oct 08 '13 at 14:00
  • It all depends upon the order that you have things in your actual code. If the timeout needs to resolve before your $http promise does, that would require the order to be switched in the tests from what I posted. – MBielski Oct 09 '13 at 15:44