How can I test a controller with resolve properties in AngularJS?

10,012

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';
    });
Share:
10,012
Kenneth Lynne
Author by

Kenneth Lynne

Systems Consultant and web-fanatic from Norway

Updated on June 07, 2022

Comments

  • Kenneth Lynne
    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.