Search code examples
angularjsunit-testingjasminekarma-runnerangular-providers

AngularJS - Karma testing with providers and injections


I am having a lot of trouble to write the unit test case for a provider that contains some injections.

The particular provider is:

(function () {
angular
    .module('core.router', [])
    .provider('routerHelper', routerHelperProvider);

routerHelperProvider.$inject = ['$stateProvider', '$urlRouterProvider'];

/**
 * This method Initializes the Router Helper provider to be used by all modules
 *
 * @param $stateProvider
 * @param $urlRouterProvider
 */
function routerHelperProvider($stateProvider, $urlRouterProvider) {

    this.$get = routerHelperService;

    routerHelperService.$inject = ['$state'];

    /**
     * This method sets the methods to be used by the helper
     *
     * @param $state
     * @returns {Object}
     */
    function routerHelperService($state) {
        var hasOtherwise = false;

        return {
            configureStates: configureStates,
            getStates: getStates
        };

        /**
         * This method configures all the states to be used by the application
         *
         * @param {!String[]} states
         * @param {!String} otherwisePath
         */
        function configureStates(states, otherwisePath) {
            states.forEach(function (state) {
                //console.log("adding state", state.state, "with config", state.config);
                $stateProvider.state(state.state, state.config);
            });
            if (otherwisePath && !hasOtherwise) {
                hasOtherwise = true;
                $urlRouterProvider.otherwise(otherwisePath);
            }
        }

        /**
         * This method returns the states to be used by the application
         *
         * @return {Object}
         */
        function getStates() {
            return $state.get();
        }
    }
}  })();

The basic unit test is:

'use strict';

describe('core.router test', function () {

    // All Service injections
    var $urlRouterProvider, $stateProvider;

    // Mocks
    var m_url = function () {
    };
    var m_state = function () {
    };

    // Others
    var routerHelper, urlRouter, state, base;

    // Before statements
    beforeEach(module('core.router', function ($provide, _routerHelperProvider_) {
        $provide.value('$urlRouterProvider', m_url);
        $provide.value('$stateProvider', m_state);
        base = _routerHelperProvider_;
    }));

    // Starting the Factory
    beforeEach(inject(function (_routerHelper_, _$urlRouter_, _$state_) {
        routerHelper = _routerHelper_;
        urlRouter = _$urlRouter_;
        state = _$state_;
    }));

    describe('when testing it', function () {
        it('should return true', function () {

            //var abc = routerHelper.getStates();

            expect(1).toEqual(1);
        });
    });
});

I keep getting errors like:

  • Error: [$injector:unpr] Unknown Provider: $stateProvider
  • Error: [$injector:unpr] Unknown Provider: $urlRouterProvider
  • Error: [$injector:unpr] Unknown Provider: routerHelperProvider

I tried several different module instantiations and several different injections, but I can't seem to make it work. When I take out the injections ($stateProvider, $urlRouterProvider and $state), the unit test is straightforward.


Solution

  • So, this would be the solution, bringing some complexity because the provider is using both the $state and $stateProvider:

    'use strict';
    
    describe('core.router test', function () {
    
        // All Provider injections
        var $urlRouterProvider, $stateProvider;
    
        // Mocks
        var m_urlProvider = mockDataCore.urlRouterProvider();
        var m_stateProvider = mockDataCore.stateProvider();
        var m_state = mockDataCore.state();
    
        // Others
        var routerHelper, base;
    
        // Define the mock providers
        beforeEach(function(){
            module(function($provide){
                $provide.provider('$urlRouter', m_urlProvider);
                $provide.provider('$state', m_stateProvider);
            });
        });
    
        // Start the module with the internal mock
        beforeEach(function () {
            module('core.router', function ($provide) {
                $provide.value('$state', m_state);
            });
        });
    
        // Load the provider with module to be able to call its configuration methods
        beforeEach(function () {
            module(['routerHelperProvider', function (_$urlRouterProvider_, _$stateProvider_, _routerHelperProvider_) {
                $urlRouterProvider = _$urlRouterProvider_;
                $stateProvider = _$stateProvider_;
                base = _routerHelperProvider_;
            }]);
        });
    
        // Inject and start the provider
        beforeEach(function () {
            inject(['routerHelper', function (_routerHelper_, $state) {
                routerHelper = _routerHelper_;
            }]);
        });
    
        // test cases
        describe('when adding one state and no "otherwise"', function () {
            it('otherwise should not be called and state should be saved to state list', function () {
    
                spyOn(m_urlProvider, "otherwise");
                spyOn(m_stateProvider, "state");
    
                var simpleState = [{
                    state : "home",
                    config : {
                        url: "/home"
                    }}];
    
                routerHelper.configureStates(simpleState);
    
                expect(m_urlProvider.otherwise).not.toHaveBeenCalled();
                expect(m_stateProvider.state).toHaveBeenCalledWith("home", {url: "/home"});
            });
        });
    
        describe('when getting the states', function () {
            it('should return the states', function () {
    
                spyOn(m_state, "get");
    
                var states = routerHelper.getStates();
    
                expect(m_state.get).toHaveBeenCalled();
            });
        });
    });
    

    The mock methods are:

    var mockDataCore = (function () {
    return {
        urlRouterProvider: urlRouterProvider,
        stateProvider: stateProvider,
        state: state
    };
    
    function urlRouterProvider() {
        return {
            otherwise: function () { /* void  */
            },
            $get: function () {  /* void  */
            }
        };
    }
    
    function stateProvider() {
        return {
            state: function () { /* void  */
            },
            $get: function () {  /* void  */
            }
        };
    }
    
    function state() {
        return {
            get: function () {
                return {};
            },
            go: function () {  /* void  */
            }
        };
    }})();
    

    Of course it doesn't cover all the tests for this provider, but the rest of them are pretty straight forward..