javascriptangularjsjasminekarma-jasmineangularjs-watch

spyOn listener watch function not working


When I try to spyOn the listener function of a $scope.$watch, is like never call the spyOn

http://jsfiddle.net/b8LoLwLb/1/

My controller

angular.module('angularApp')
    .controller('MainCtrl', function ($scope) {
        $scope.name = '';

        this.changeName = function () {
            console.log('the name has change to ' + $scope.name);
        };

        $scope.$watch('name', this.changeName);
    });

My test

describe('Controller: MainCtrl', function () {

    // load the controller's module
    beforeEach(module('angularApp'));

    var MainCtrl,
        scope;

    // Initialize the controller and a mock scope
    beforeEach(inject(function ($controller, $rootScope) {
        scope = $rootScope.$new();
        MainCtrl = $controller('MainCtrl', {
            $scope: scope
        });
    }));

    it('should check if watcher was triggered', function () {
        // Spy the listener funtion
        spyOn(MainCtrl, 'changeName');

        // Change the watched property
        scope.name = 'facu';

        // Digest to trigger the watcher.
        scope.$digest();

        // Expect the function to have been called
        expect(MainCtrl.changeName).toHaveBeenCalled();
    });
});

The problem is that instead of spying the function, the test execute it and print the console log.

I'm using angular 1.4


Solution

  • This is expected behavior, it has nothing to do with jasmine or angular but to do with the function reference held by the property. When you do $scope.$watch('name', this.changeName) on the controller instantiation, the function reference held by this.changeName(at that time) is set to be watched. Even if you spyOn the function on the controller instance (later), the function reference held by the property changeName of controller instance is only changed (to the wrapper function created by jasmine to track the call) but not of the watcher as it still uses the original function reference. So when watch executes it just runs the actual function reference not the spy func reference later you set on changeName property.

    Instead if you do this in your controller:

       var vm = this;
       $scope.$watch('name', function(){
           vm.changeName();
       });
    

    You will see your test passing.