Karma Testing with Angular.js + UI Router
angular-ui-router, angularjs, javascript, karma-runner
Solution
The reason for your error is that you are first transitioning to a state which instantiates your `UsersIndexCtrl` with a new scope, but then subsequently creating another instance of the controller (again, with a new scope) within the test. The two are independent of one another, and in the second instance, `stateModels` is an unknown/unavailable dependency.
So, while your ideas are valid testing concerns, in trying to assert all three together you are essentially performing an end-to-end test in a unit test environment. You should not want to - doing so will create a brittle dependency on:
- the controller belonging to the "users" state
- that somewhere in the state transition a particular http request is called
- that the stateModels dependency is bound to the scope.
Of these assertions, only the last is of any concern to the controller. Unit tests for your controller don't care how/when it was instantiated or where the `stateModels` dependency came from, they are only concerned with the controller's own behaviour. So, let's split this behaviour up:
Unit testing the controller
Your first test should be reduced to the following:
it('binds the users to the scope', function(){
var stateModels = [{}, {}, {}];
$controller('UserIndexCtrl', {$scope: $scope, stateModels: stateModels});
assert.equal($scope.users, stateModels);
});
Note that as you add more tests, you will likely want to move your controller instantiation to a `beforeEach` block.
Testing the route
The concern of testing a route is really that of application behaviour, for which you should defer to Protractor. However, if you particularly want to perform a unit test on a state, this is perhaps best served by testing the configuration of the state itself. For example:
it('resolves the stateModels dependency', function() {
var state = $state.get('users');
assert.isDefined(state.resolve.stateModels);
// perform assertion that stateModels function resolves to what is expected
// Note: any such assertion should stub any dependency being used, to ensure
// we are testing in isolation.
});
That notwithstanding, I personally don't elect to unit test routing/route configuration, and instead acquire such coverage through e2e testing with Protractor.
Problem
I was wondering how should one test with Karma test with Angular.js + UI router? I have the following state defined: Which has two resolves that fetches some data and prepares the data for the controller. (Coming from Ember background, this makes a lot of sense.) ``` $stateProvider .state('users', { resolve: { getData: function (User) { return User.query().$promise }, stateModels: function (getData) { var models = {} models.users = getData return models } }, url: '/', templateUrl: '/views/users/index.html', controller: 'UsersIndexCtrl' }) ``` Our UserIndexCtrl looks like: (Which takes the resolved stateModels and assigns it to the $scope, so the view can use it) ``` app.controller('UsersIndexCtrl', [ '$scope', '$state', 'stateModels', function ($scope, $state, stateModels) { $scope.users = stateModels.users }]) ``` This is working great in the browser, I am seeing the right results. However, when it comes to testing it is giving me odd errors. Here is an example Karma unit test: ``` describe('controllers', function () { var $httpBackend , $rootScope , $scope , $state , $httpBackend , $controller beforeEach(module('app')) beforeEach(inject(function ($injector) { $state = $injector.get('$state') $rootScope = $injector.get('$rootScope') $httpBackend = $injector.get('$httpBackend') $scope = $rootScope.$new() $controller = $injector.get('$controller') })) it('UserIndexCtrl should exist', inject(function () { $httpBackend .expect('GET', '/api/users') .respond(200, {users: [ {}, {}, {} ]}) $state.go('users') $rootScope.$apply() $controller('AdminZonesIndexCtrl', { $scope: $scope, $state: $state }); $rootScope.$apply() assert.equal($scope.users.length, 3) })) }) ``` And I am seeing: ``` [$injector:unpr] Unknown provider: stateModelsProvider <- stateModels http://errors.angularjs.org/1.3.0-build.2937+sha.4adc44a/$injector/unpr?p0=stateModelsProvider%20%3C-%20stateModels Error: [$injector:unpr] Unknown provider: stateModelsProvider <- stateModels http://errors.angularjs.org/1.3.0-build.2937+sha.4adc44a/$injector/unpr?p0=stateModelsProvider%20%3C-%20stateModels ``` The idea here is: - We mock out the API request so that GET requests to /api/users will return an array of 3 objects. - We go to the state named users - We expect to see that the $scope.users should be an array of 3 objects. - From this test, we tested both the resolves defined in the router, and that the controller assigned the resolved objects correctly. Thanks Bill