Katana24
Katana24

Reputation: 8959

TypeError on a controller function`

I'm running a single test on my controller to determine if it's properly defined but I keep getting a TypeError: undefined on the controller object. Here's the complete error:

Search Controller
  should have the controller defined <<< FAILURE!
* TypeError: 'undefined' is not an object (evaluating 'myMenuDataLoad.then')
* Expected undefined to be defined.

And here is the controller to be tested:

myAppControllers.controller('VisibilitySearchController', ['$scope', 'headerService', 'menuService', 'navigationService', function($scope, headerService, menuService, navigationService ){

headerService.setTitle('My title');

var myMenuDataLoad = menuService.loadData('partials/common/components/menu-bar/json/menu-bar.json');

myMenuDataLoad.then(function(dataResult){
    menuService.setData(dataResult.data);
});

var myNavDataLoad = navigationService.loadData('partials/common/components/navigation-bar/json/navigation-bar.json');
myNavDataLoad.then(function(dataResult){
    navigationService.setData(dataResult.data);
});


}]);

I've initialized the controller by passing it everything it needs in its parameters i.e. scope, headerService, menuService and navigationService - I mock these services using the jasmine.createSpyObj method and pass in all the relevant methods ( the ones used on the controller ):

// Mock our services
beforeEach(function() {

    // Methods are accepted as the 2nd second parameter
    headerService = jasmine.createSpyObj('headerService', ['setTitle']);
    module(function($provide) {
        $provide.value('headerService', headerService);
    });

    menuService = jasmine.createSpyObj('menuService', ['loadData', 'setData']);
    module(function($provide) {
        $provide.value('menuService', menuService);
    });

    navigationService = jasmine.createSpyObj('navigationService', ['loadData', 'setData']);
    module(function($provide) {
        $provide.value('navigationService', navigationService);
    });
});

And the actual initialization of the controller happens here:

beforeEach(inject(function($rootScope, $injector, $controller, _headerService_, _menuService_, _navigationService_) {

    scope = $rootScope.$new();

    // Instantiate the controller
    searchController = $controller('VisibilitySearchController', {
        $scope : scope,
        headerService : headerService,
        menuService : menuService,
        navigationService : navigationService
    });
}));

So what am I doing wrong here? Why isn't the test (see below) passing?

it("should have the controller defined", function() {
    expect(searchController).toBeDefined();
});

Have I mocked the services correctly? What action needs to be done on a local controller variable in order to properly initialize them and the methods they are used in?

Thanks!


UPDATE

I've looked further into this but am unfortunately still receiving the same undefined error. When you create a mock object of a service do you have to provide that service with all of its dependencies and methods you make use of? For example:

 menuService = jasmine.createSpyObj('menuService', ['$parse','$q', 'dataService', 'loadData', 'then']);
    module(function($provide) {
        $provide.value('menuService', menuService);
    });

Here when I create the mock object I provide it with all the dependencies it would expect plus I added in two functions that I make use of in the controller. So how do I go about mocking a function in a mocked object? I tried this but I'm still getting the same error:

menuService.loadData = jasmine.createSpy( 'loadData()' ).andReturn( data );

Upvotes: 0

Views: 672

Answers (1)

David Bohunek
David Bohunek

Reputation: 3201

As mentioned in the comment your menuService.loadData() will always return undefined so evaluating expression myMenuDataLoad.then will always fail as mentioned in the error. What you must do is to provide an implementation of menuService.loadData which will return a promise. You can do the mocking the way you did it in case you want these method to be called but you don't rely on any return value of it. If you need the method to return something you can do define menuService this way:

var menuService = {
    loadData: function() {  
        var deferred = $q.defer();      
        var data = []; //put any data you need here to be returned within the promise       

        deferred.resolve{data);             
        return deferred.promise;    
    }
}

module(function($provide) {
        $provide.value('menuService', menuService);
    });

You will need instance of $q which you can get in your inject call similarly to $rootScope, $injector etc.

In case you wanted to spy on menuService.load function you can do it this way:

spyOn(menuService, "loadData").andCallThrough()

That will keep your mocked implementation of the method but still allow you to assert it was called etc. I don't think you need it.

Upvotes: 1

Related Questions