Unit Testing dependency injection

All we need is an easy explanation of the problem, so here it is.

I am brand new to jasmine and karma. I believe I have the environment setup properly and I am able to run very basic unit tests, but as soon as I try to instantiate a controller I am given an unknown provider error and I am unsure how to debug this. Do I need to pass in a stateProvider dependency? I don’t see this in the angular-seed example.

Bower.json:

{
"name": "starter",
  "description": "A starter project for AngularJS",
  "version": "2.0.0",
  "homepage": "https://starter.com",
  "private": true,
  "dependencies": {
      "angular": "1.2.x",
      "angular-route": "1.2.x",
      "angular-loader": "1.2.x",
      "angular-mocks": "~1.2.15"
  }
}

Home Controller:

angular.module('home').controller('Home', function($scope, $rootScope, $state) {

    console.log($scope.pageType);

    $rootScope.pageType = 'home';

    /*
     * Takes in a state and transitions the app to that state.
     */
    $scope.goTo = function(value) {
        $state.transitionTo(value);
    }

    /*
     * Handles what happens after clicking log-in
     */
    $scope.loginClicked = function() {
        $state.transitionTo('log-in');
    }
});

Test file:

'use strict';

/* jasmine specs for controllers go here */

describe('Home', function() {
    beforeEach(module('home'));

    it('should run tests', inject(function() {
        expect(null).toBeDefined();
    }));

    it('should not say true equals false', function() {
        expect(false).not.toBe(true);
    });

    it('should say true equals true', function() {
        expect(true).toBe(true);
    });

    it('should say false does not equal true', function() {
        expect(false).not.toBe(true);
    });

    it('should create "phones" model with 3 phones', inject(function($controller,$rootScope) {

    /*
     * 
     * COMMENTING OUT THESE LINES = PASS
     *
     */
        var scope = $rootScope.$new(),
            ctrl = $controller('Home', {$scope:scope});
        expect(ctrl).not.toBe(null);
    }));

});

Error:

    Error: [$injector:unpr] Unknown provider: $stateProvider <- $state
http://errors.angularjs.org/1.2.16/$injector/unpr?p0=%24stateProvider%20%3C-%20%24state
    at /Users/jlett/bower_components/angular/angular.js:78:12
    at /Users/jlett/bower_components/angular/angular.js:3705:19
    at Object.getService [as get] (/Users/jlett/bower_components/angular/angular.js:3832:39)
    at /Users/jlett/bower_components/angular/angular.js:3710:45
    at getService (/Users/jlett/bower_components/angular/angular.js:3832:39)
    at invoke (/Users/jlett/bower_components/angular/angular.js:3859:13)
    at Object.instantiate (/Users/jlett/bower_components/angular/angular.js:3880:23)
    at /Users/jlett/bower_components/angular/angular.js:7134:28
    at null.<anonymous> (/Users/jlett/test/unit/home-controller_tests.js:26:20)
    at Object.invoke (/Users/jlett/bower_components/angular/angular.js:3869:17)
Error: Declaration Location
    at window.inject.angular.mock.inject (/Users/jlett/bower_components/angular-mocks/angular-mocks.js:2132:25)
    at null.<anonymous> (/Users/jlett/test/unit/home-controller_tests.js:24:54)
    at /Users/jlett/zoetis-rimadyl-mobile/test/unit/home-controller_tests.js:5:1

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

You’ll get this error if one of the injectables module isn’t included.

For instance, you have

beforeEach(module('home'));

If your $state dependency is not in the home module, you’ll need to include that module also. I’m not familiar with $state (I think it’s angular-ui’s router? Only angular.js services are supposed to start with $). If it’s angular ui, this is how you should setup:

beforeEach(module('ui.router'));
beforeEach(module('home'));

This way, angular’s test runner knows what modules are required to run your tests.

Really, the inclusion of the home module should do this for you as long as you have the ui.router dependency defined as a dependency of that module. If you have that configured correctly, you may need to look at the order of your files being included for your tests. For example, make sure the ui-router file is being included for your tests and that it is referenced before your home module in karma’s config.

Method 2

As you are including the $state dependency in your controller – you need to provide the $state in the controller unit test.

    var $scope = $rootScope.$new(),
    ctrl = $controller('Home', {
               $scope: $scope,
               $state: {} //Or inject the state using the injector service and then you can use some jasmine spies to mock the function calls or just to spy on 'em.
          });
    expect(ctrl).not.toBe(null);

Your it block with the changes….

it('should create "phones" model with 3 phones', inject(function($controller, $rootScope, $state) {

    /*
     * 
     * COMMENTING OUT THESE LINES = PASS
     *
     */
        var scope = $rootScope.$new(),
            ctrl = $controller('Home', {$scope:scope, $state: $state});
        expect(ctrl).not.toBe(null);
    }));

However at the setup of my unit tests I like to create function for controller setup as I describe in this setup here.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply