How can I test a controller with resolve properties in AngularJS?
Solution 1
It was eventually solved by converting everything to services, as suggested by charlietfl.
Example:
Route config:
//This helper injects a function with the service
//defined in the initMethod string and returns services.prepare()
var interceptWith = function(initMethod) {
return [initMethod, function(m) {
return m.prepare();
}];
}
$routeProvider
.when('/foobar/', {
templateUrl: 'foobar.html',
controller: 'FoobarCtrl',
resolve: {
init: interceptWith('FoobarCtrlInit')
}
});
The foobar controller definition:
angular.module('fooApp').controller('FoobarCtrl', ['$scope', 'init', function ($scope, init) {
$scope.data = init.data;
}])
.service('FoobarCtrlInit', ['$q', '$timeout', function ($q, $timeout) {
var _prepare = function() {
var deferred = $q.defer();
//Fake async loading of data
$timeout(function() {
deferred.resolve({data: ['A','B','C']});
}, 1000);
return deferred.promise;
}
return {
prepare: _prepare
}
}]);
To test this, one could do this:
'use strict';
describe('Controller: FoobarCtrl', function() {
// load the controller's module
beforeEach(module('fooApp'));
var FoobarCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function($controller) {
scope = {};
CourseCtrl = $controller('FoobarCtrl', {
$scope: scope,
init: {data: ['Testdata A', 'B', 'C']}
});
}));
it('should attach a list of data to the scope', function() {
expect(scope.data.length).toBe(3);
});
});
Solution 2
Ran into the same thing here. I solved it using the approach here: https://groups.google.com/forum/?fromgroups=#!topic/angular/LzXm-9nwkjY.
Basically, I mocked the data that would normally be sent by using a simple variable and added it to the controller in the test. In your case I assume it would look something like:
var initData = {
contents: [{message: 'Hello!'}]
};
$controller("ContentCtrl", { $scope: ..., init: initData });
Solution 3
I had the same error in Karma when using resolve on $routeProvider, I fixed it by testing my resolve in the unit test for app.js, like this:
describe("myApp", function() {
beforeEach(module('myApp'));
it('should resolve initial values for my Controller', inject(function( $route ) {
expect($route.routes['/'].resolve.init).toBeDefined; //or whatever test you want
}));
});
And then I just mocked the value on the test for my controller, like this inside the describe for the controller:
//mock out the resolved values to isolate controller code
beforeEach(module(function($provide) {
$provide.value('init', function() {
return 'whatever data you need to mock';
});
Comments
-
Kenneth Lynne about 2 years
How can one test a controller with resolve properties? It throws an error: Unknown provider: InitProvider, during testing, understandably. How can I test it?
I use the init property in the route config to load data and pass it along to the controller at controller instantiation so the route doesn't change before data is loaded.
$routeProvider .when('/topic/:topic_id/content/:content_id', { templateUrl: 'views/content.html', controller: 'ContentCtrl', resolve: { init: ContentCtrl.init } });
Is the pattern completely wrong in the first place?
'use strict'; var ContentCtrl = ['$scope', '$location', '$routeParams', 'init', function ($scope, $location, $routeParams, init) { $scope.contents = init.contents; }]; ContentCtrl.init = ['$q', 'app_config', '$log', '$timeout', function ($q, app_config, $log, $timeout) { var defer = $q.defer(); $log.log("ContentCtrl loading.."); $timeout(function() { defer.resolve({contents: [ {message: 'Hello!'} ]}); $log.log("ContentCtrl loaded."); }, 2000); return defer.promise; }]; angular.module('studentportalenApp').controller('ContentCtrl', ContentCtrl);
I want to encapsulate the whole controller inside
.controler('ContentCtrl', function() { ... })
, but have yet to figure out how this is done correctly to make the init available in the route configuration.