简体   繁体   中英

How do I test that this nested function was called in an AngularJS unit test?

I'm not new to unit testing (C#) but I am very new to unit testing in AngularJS. I'm trying to test my controller and so far have been able to get several tests to work properly, however there are some that are proving to be rather difficult.

I have $scope method that makes a call to our Authentication service which returns a promise. In the "then" function I am checking to see if the user was indeed authenticated and based on that I will call a private function that will go out and make other Service calls.

Currently the test is failing with the following error:

Expected spy getConfigurationStatuses to have been called.
Error: Expected spy getConfigurationStatuses to have been called.

If anyone can please help point me in the right direction I would really appreciate it. I'll post the code below -Thanks for any help!

Here's my specs (the one that is not working is the "should call specific configurations if user is authenticated" spec:

describe('EnvironmentCtrl specs', function(){
    var $rootScope = null, $scope = null, ctrl = null;    
    var Authentication = {
        getCredentials: function(){ return true; }
    };

    var Environment = { getConfigurationStatuses: function(){ return true; } };

    beforeEach(module('ngRoute'));
    beforeEach(module('environment'));

    beforeEach(module(function($provide){
        $provide.value('SITE_ROOT', '/');
    }));

    beforeEach(inject(function(_$rootScope_, _$controller_, _$timeout_, _$location_, _$q_, _Authentication_, _Environment_){

        $rootScope = _$rootScope_;
        $controller = _$controller_;
        $timeout = _$timeout_;
        $location = _$location_;
        $q = _$q_;
        Authentication = _Authentication_;
        Environment = _Environment_;

        spyOn(Authentication, 'getCredentials').andCallThrough();
        spyOn(Environment, 'getConfigurationStatuses').andCallThrough();

        $rootScope = _$rootScope_;
        $scope = $rootScope.$new();

        ctrl = $controller('EnvironmentCtrl', {$rootScope: $rootScope,$scope: $scope, $timeout: $timeout,
            Eventor: {}, Controller: {}, Environment: Environment,Authentication: Authentication, ErrorService:{} });

    }));

    describe('When initializing the EnvironmentCtrl', function(){

        // this one works fine!
        it('should set default values on the scope object', function(){
            expect($scope.controllerName).toEqual('EnvironmentCtrl');
            expect($scope.environmentStatusType).toEqual('configurations');
            expect($scope.configurationsSelected).toBe(true);
            expect($scope.isDataLoaded).toBe(false);
        });

        // this works fine!
        it('should make a call to authenticate the user', function(){
            $scope.determineViewToDisplay();
            expect(Authentication.getCredentials).toHaveBeenCalled();
        });

        // this one doesn't work!
        it('should call specific configurations if user is authenticated', function(){
            $scope.determineViewToDisplay();
            $rootScope.isUserAuthenticated = true;
            expect(Environment.getConfigurationStatuses).toHaveBeenCalled();
        });
    });
});

Here's the three functions that are involved in the unit tests:

$scope.determineViewToDisplay = function () {
    Authentication.getCredentials().then(function(){
        if ($rootScope.isUserAuthenticated === true) {
            $scope.isAnonymous = false;
            handleAuthenticatedUserView();
        } else {
            Eventor.publish('event:login', false);
            $scope.isAnonymous = true;
            handleAnonymousUserView();
        }
    }, function(err){
        ErrorService.handleError(err, null, $scope.controllerName);
    });
};

function handleAuthenticatedUserView() {
    $scope.configurationStatusTimer = $timeout(function(){
        displayConfigurationStatuses(true);
    }, 5);
}
function displayConfigurationStatuses(isAuthenticated) {
    Environment.getConfigurationStatuses(isAuthenticated).then(function(statuses){
            setConfigurationsIconStatus(statuses);
            $scope.configurationStatuses = statuses;
            $scope.isDataLoaded = true;
            amplify.store($rootScope.productCustomerName + '-configurationStatuses', statuses, {expires: 120000});            
            $rootScope.showLoadingIndicator = false;
        }, function(err){
            ErrorService.handleError(err, null, $scope.controllerName);
        });
}

It looks like determineViewToDisplay() only calls handleAuthenticatedUserView if

$rootScope.isUserAuthenticated === true 

but you're not setting $rootScope.isUserAuthenticated to true until after you call

$scope.determineViewToDisplay()

so handleAuthenticatedUserView() never gets called, and in turn displayConfigurationStatuses() never gets called.

I know this is an older question but...

Could it be the fact that the getCredentials method is actually an async operation? That is why you will see the 'should make a call to authenticate the user' pass, because the call to that function is synchronous. getCredentials returns a promise and keeps going. This causes your assertions to run before the resolve/reject handlers run (which is where your function under test is eventually called).

You can use the 'done' (or older 'runs/waitsFor') syntax in Jasmine with async operations to ensure your assertions aren't run until all promises have been resolved.

Also I just noticed that displayConfigurationStatuses is called within a timeout (more async). You may have to mock the $timeout service to execute immediately or maybe make handleAnonymousUserView return a promise that is resolved once the timeout executes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM