Healforgreen
Healforgreen

Reputation: 579

Jasmine: Controller is defined, but its methods are undefined

I am testing an AngularJS directive's controller.

describe('The directive', function() {
    var element,
        scope;

    beforeEach(module('app'));
    beforeEach(module('path/to/theDirective'));

    beforeEach(inject(function($compile, $rootScope) {
        element = angular.element('<div args="args" the-directive ></div>');
        scope = $rootScope;
        $compile(element)(scope);
        scope.args = {
            availableValues : [1, 2, 3],
            key : 'id',
            selectedValues : [],
            searchText : '',
            flag: false
        };
        scope.$digest();        
    }));

    it('should compile', function() {
        expect(element.html()).not.toBeNull();
    });

    describe('directive controller', function() {
        var controller;

        beforeEach(inject(function($controller) {
            controller = element.controller('theDirective', {
                $scope: scope
            });
        }));

        it('should exist', function() {
            expect(controller).not.toBeNull();
            expect(controller).toBeDefined();
            expect(scope.disableAddButton()).toBeDefined();
        });
    });
});

The first it block passes, so the directive is compiling successfully. The second describe block is not passing. The first two assertions in the second describe's it block pass, but the third one does not. It's returning TypeError: 'undefined' is not a function (evaluating 'scope.disableAddButton()'). Is it possible that the controller is not being injected correctly, or is there more setup that needs to be done?

Upvotes: 1

Views: 921

Answers (1)

Healforgreen
Healforgreen

Reputation: 579

It turns out that the scope needs to be isolated since the directive's controller is a private function.

Adding scope = element.isolateScope() || element.scope(); after scope.$digest(); does the trick. Also, moving the controller declaration to the first beforeEach block isn't a bad idea.

The fixed test would look like this:

describe('The directive', function() {
    var element,
        scope,
        theController;

    beforeEach(module('app'));
    beforeEach(module('path/to/theDirective'));

    beforeEach(inject(function($compile, $rootScope) {
        element = angular.element('<div args="args" the-directive ></div>');
        scope = $rootScope;
        $compile(element)(scope);
        scope.args = {
            availableValues : [1, 2, 3],
            key : 'id',
            selectedValues : [],
            searchText : '',
            flag: false
        };
        scope.$digest();    
        theController = element.controller('theDirective');     
        scope = element.isolateScope() || element.scope();
    }));

    it('should compile', function() {
        expect(element.html()).not.toBeNull();
    });

    describe('directive controller', function() {
        it('should exist', function() {
            expect(theController).not.toBeNull();
            expect(theController).toBeDefined();
            expect(scope.disableAddButton()).toBeDefined();
        });
    });
});

Upvotes: 1

Related Questions