Spy on scope function that executes when an angular controller is initialized

16,858

Solution 1

Setting the spy before controller instantiation (in the beforeEach) is the way to test controller functions that execute upon instantiation.

EDIT: There is more to it. As a comment points out, the function doesn't exist at the time of ctrl instantiation. To spy on that call you need to assign an arbitrary function to the variable (in this case you assign scope.getResponses to an empty function) in your setup block AFTER you have scope, but BEFORE you instantiate the controller. Then you need to write the spy (again in your setup block and BEFORE ctrl instantiation), and finally you can instantiate the controller and expect a call to have been made to that function. Sorry for the crappy answer initially

Solution 2

You need to initialise the controller yourself with the scope you've created. The problem is, that you need to restructure your code. You can't spy on a non-existing function, but you need to spyOn before the function gets called.

$scope.loadResponses = function(){
    //do something
}
// <-- You would need your spy attached here
$scope.loadResponses();

Since you cannot do that, you need to make the $scope.loadResponses() call elsewhere.

The code that would successfully spy on a scoped function is this:

var scope;
beforeEach(inject(function($controller, $rootScope) {
    scope = $rootScope.$new();
    $controller('aCtrl', {$scope: scope});
    scope.$digest();
}));
it("should have been called", function() {
    spyOn(scope, "loadResponses");
    scope.doTheStuffThatMakedLoadResponsesCalled();
    expect(scope.loadResponses).toHaveBeenCalled();
});

Solution 3

The only way I have found to test this type of scenarios is moving the method to be tested to a separate dependency, then inject it in the controller, and provide a fake in the tests instead.

Here is a very basic working example:

angular.module('test', [])
    .factory('loadResponses', function() {
        return function() {
            //do something
        }
    })
    .controller('aCtrl', ['$scope', 'loadResponses', function($scope, loadResponses) {
        $scope.loadResponses = loadResponses;

        $scope.loadResponses();
    }]);

describe('test spec', function(){
    var scope;
    var loadResponsesInvoked = false;

    var fakeLoadResponses = function () {
        loadResponsesInvoked = true;
    }

    beforeEach(function () {
        module('test', function($provide) {
            $provide.value('loadResponses', fakeLoadResponses)
        });

        inject(function($controller, $rootScope) {
            scope = $rootScope.$new();
            $controller('aCtrl', { $scope: scope });
        });
    });

    it('should ensure that scope.loadResponses was called upon instantiation of the controller', function () {
        expect(loadResponsesInvoked).toBeTruthy();
    });
});

For real world code you will probably need extra work (for example, you may not always want to fake the loadResponses method), but you get the idea.

Also, here is a nice article that explains how to create fake dependencies that actually use Jasmine spies: Mocking Dependencies in AngularJS Tests

EDIT: Here is an alternative way, that uses $provide.delegate and does not replace the original method:

describe('test spec', function(){
    var scope, loadResponses;
    var loadResponsesInvoked = false;

    beforeEach(function () {
        var loadResponsesDecorator = function ($delegate) {
            loadResponsesInvoked = true;
            return $delegate;
        }

        module('test', function($provide) {
            $provide.decorator('loadResponses', loadResponsesDecorator);
        });

        inject(function($controller, $rootScope) {
            scope = $rootScope.$new();
            $controller('aCtrl', { $scope: scope });
        });
    });

    it('should ensure that scope.loadResponses was called upon instantiation of the controller', function () {
        expect(loadResponsesInvoked).toBeTruthy();
    });
});
Share:
16,858

Related videos on Youtube

Matt MacLeod
Author by

Matt MacLeod

Updated on July 01, 2022

Comments

  • Matt MacLeod
    Matt MacLeod about 2 years

    I want to test that the following function is in fact called upon the initialization of this controller using jasmine. It seems like using a spy is the way to go, It just isn't working as I'd expect when I put the expectation for it to have been called in an 'it' block. I'm wondering if there is a special way to check if something was called when it wasn't called within a scope function, but just in the controller itself.

     App.controller('aCtrl', [ '$scope', function($scope){
    
        $scope.loadResponses = function(){
            //do something
        }
    
        $scope.loadResponses();
    
    }]);
    

    //spec file

    describe('test spec', function(){
    
        beforeEach(
        //rootscope assigned to scope, scope injected into controller, controller instantiation.. the expected stuff
    
            spyOn(scope, 'loadResponses');
        );
    
        it('should ensure that scope.loadResponses was called upon instantiation of the controller', function(){
             expect(scope.loadResponses).toHaveBeenCalled();
        });
    });
    
    • PSL
      PSL almost 10 years
      It will generally work but not in this case. Because you set up spy after controller has been instantiated and has invoked the method. Even if you create a spy method on scope it will be overwritten while controller is instantiated and it wont really work.
    • PSL
      PSL almost 10 years
      Alright in that case loadResponses calls a service correct? you can create a mock (spy object) for that service (which is a dependency on the controller) method, and once controller is initialized you can test if that method has been called. It will make sure even if your method name changes (or what not) the required ser4vice call has been made on initialization. But i guess there is no surprise (as far as i know) why you can't spy on something that does not exist yet. Spy is nothing but jasmine overrides the method that you spy on to check for expectation, no other magic there.
    • Matt MacLeod
      Matt MacLeod
      Down the road I don't want a developer to remove it (purposely or not) because it gets a resource critical for use of the page. I'm able to check that a variable is assigned the value expected after initialization, I'm a little surprised I'm not able to use a spy to check an initialization call.
  • PSL
    PSL almost 10 years
    loadResponses is called in the controller initialization. That was the whole point of the question as i understand I want to test that the following function is in fact called upon the initialization of this controller. It seems like a chicken<->egg situation.
  • Will Buck
    Will Buck almost 9 years
    Zenorbi's answer has a lot more detail on how to set this up properly, but this is the correct answer. Before calling scope.$digest in Zenorbi's beforeEach, setup your spy with spyOn(scope, 'loadResponses'), and you should be in good shape.
  • guy mograbi
    guy mograbi over 8 years
    that does not make any sense to me.. need example. before init, the function does not exist - so nothing to spy on. if we assign a spy beforehand, it will be overridden in init..
  • Matt MacLeod
    Matt MacLeod over 8 years
    @guymograbi I've updated the answer to better explain... the previous answer was misleading, I apologize.