Search code examples
angularjsjasmineangular-servicesangularjs-watch

AngularJS watcher in constructor not fired while testing


I created a factory which returns a constructor. The constructor includes a watcher looking at a property of the instance. When that property is changed in tests, the watcher is not fired. Outside of tests, it is fired. $rootScope.$apply doesn't seem to help.

Here is a jsfiddle which contains a simplified version of my code and tests.

http://jsfiddle.net/2Ny8x/234/

//--- CODE --------------------------
(function (angular) {
    var myApp = angular.module('myApp', []);

    myApp.factory('MyObject', function($rootScope) {
        function MyObject() {
            this.prop = 5;
            this.watcherCallback = angular.noop;

            $rootScope.$watch(this.prop, this.watcherCallback);
        }

        return MyObject;
    });
})(angular);


// ---SPECS-------------------------
describe('MyObject', function () {
    beforeEach(module('myApp'));

    var MyObject, rootScope;
    beforeEach(inject(function (_MyObject_, $rootScope) {
        MyObject = _MyObject_;
        rootScope = $rootScope;
    }));

    it('calls the watcher when "prop" updates', function() {
        var newObj = new MyObject();
        spyOn(newObj, 'watcherCallback');

        newObj.prop = 10;
        rootScope.$apply();

        expect(newObj.watcherCallback).toHaveBeenCalled();
    });
});

Why doesn't the watcher fire?


Solution

  • var newObj = new MyObject();
    

    This assigns noop to newObj.watcherCallback. And it registers newObj.watcherCallback (so, noop) as a watcher caallback.

    spyOn(newObj, 'watcherCallback');
    

    This replaces newObj.watcherCallback by another function, which allows you to know when this new spy function is called. But it doesn't register this spy function as a watcher callback to the rootScope. What is register is the original one: noop.

    So, when you change the value of the property and call $apply(), the rootScope still has a reference to the noop function and calls it. Your spy is unaware of it.

    It should work fine if you do in the constructor:

    var that = this;
    $rootScope.$watch(this.prop, function() {
        that.watcherCallback();
    });