March 06 2015

AngularJS - Unit Testing code that uses $timeout

While working through writing unit tests today I reached some code in a controller that displays an alert message and then hides it after five seconds with the angular $timeout service, here's a cut down version of the controller:

angular
    .app('app')
    .controller('myController', myController);

myController.$inject = ['$timeout'];

function myController($timeout) {
    var vm = this;

    vm.alertVisible = false;
    vm.showAlert = showAlert;

    function showAlert(){
        // show alert
        vm.alertVisible = true;

        // hide alert after 5 seconds
        $timeout(function(){
            vm.alertVisible = false;
        }, 5000);
    }
}

 

Sinon.JS Fake Timer doesn't work with AngularJS $timeout

I'm using Sinon.JS in my unit tests so I tried using a fake timer to artificially tick the clock 5000ms but this didn't work, and after some further testing I found that the Sinon fake timer works with the native javascript setTimeout function but not with the AngularJS $timeout service.

 

Solution - Use $timeout.flush()

The solution turned out to be much simpler than setting up fake timers, I just needed to flush the queue of the $timeout service by calling $timeout.flush(), here's the snippet from the unit tests:

describe('controller: myController', function(){
    describe('showAlert', function(){
        beforeEach(function(){
            // Arrange
            vm.alertVisible = false;

            // Act
            vm.showAlert('test alert message');
        });

        it('should show the alert', function(){
            // Assert
            assert.isTrue(vm.alertVisible);
        });

        it('should hide the alert after 5 seconds', function(){
            // Act - flush $timeout queue to fire off deferred function
            $timeout.flush();

            // Assert
            assert.isFalse(vm.alertVisible);
        });
    })
});

Sponsored by