Tests suites tend to grow together with applications. It is quite common that you need a significant amount of time to execute all your unit tests. Under these circumstances, you cannot afford to waste 5 secons on waiting for a setTimeout callback. Technically, even if you could, increasing the waiting time even further would eventually cause a timeout, resulting in a failed test.

We still need to find a way to test functionality where a setTimeout or a setInterval is present. This is what SinonJs fake timers are for. We can freeze time with SinonJs and fast-forward it to a time instant we would like to verify.

Freezing the Timer

Freezing the timer is done by calling the useFakeTimers method of the sinon object. Don’t forget to store the reference of the timer.

var clock = sinon.useFakeTimers(); 1 2 3 var clock = sinon . useFakeTimers ( ) ;

As soon as you call the useFakeTimers method, time will stop. You can start executing the tested code and verify the intended behavior after you make some time pass. For instance, the following code won’t do anything as the timer is not running. Don’t wait for the console log to appear.

setTimeout( function() { console.log('One second has elapsed.'); }, 1000 ); 1 2 3 setTimeout ( function ( ) { console . log ( 'One second has elapsed.' ) ; } , 1000 ) ;

Time can be advanced by calling the tick method of your clock object and passing it a value in milliseconds.

clock.tick( 1000 ); 1 2 3 clock . tick ( 1000 ) ;

You will always know how much fake time has passed since creating the clock object. The tick method returns the number of fake milliseconds passed since useFakeTimers was called. Therefore, you can formulate your expectations at the right time even if there are complex timing dependencies in your application.

Make sure you use the right boundary values in your tests. If an animation takes 2 seconds to execute, don’t advance the clock by 7.5 seconds. Similarly to your CSS files, your tests should not contain magic numbers. Therefore, using the boundary value of 2 seconds is suggested before verifying that the animation is done.

Once you don’t need the fake timer, restore it:

clock.restore(); 1 2 3 clock . restore ( ) ;

Use cases and constraints

Use cases:

Animations

Verifying periodic polling updating data on our page

Any timing constraints formulated by setTimeout and setInterval

Fake timers save you time. Your test suite does not have to wait until the setTimeout or setInterval callback is fired.

Constraints:

Do not expect the fake timer to freeze ongoing animations

Always test your functionality moving forward in time, rewinding is not reliable

Testing timing constraints

Suppose the following CSS class is given.

.moving-square { width: 50px; height: 50px; position: absolute; top: 0px; left: 0px; background-color: #ee0000; } 1 2 3 4 5 6 7 8 9 10 . moving - square { width : 50px ; height : 50px ; position : absolute ; top : 0px ; left : 0px ; background - color : # ee0000 ; }

Let’s move a square on the screen and verify its position.

function playAnimation( $element ) { var x = 0; var maxX = $( 'body' ).width() - 50; var direction = 1; var dt = 20; // 50fps var dx = 10; // 10 pixels per 20 milliseconds var animate = function() { if( x <= 0 ) { direction = 1; } else if( x >= maxX ) { direction = -1; } x += dx * direction; $element.css( 'left', x + 'px' ); } setInterval( animate, dt ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function playAnimation ( $ element ) { var x = 0 ; var maxX = $ ( 'body' ) . width ( ) - 50 ; var direction = 1 ; var dt = 20 ; // 50fps var dx = 10 ; // 10 pixels per 20 milliseconds var animate = function ( ) { if ( x <= 0 ) { direction = 1 ; } else if ( x >= maxX ) { direction = - 1 ; } x += dx * direction ; $ element . css ( 'left' , x + 'px' ) ; } setInterval ( animate , dt ) ; }

The associated tests show how the animation can be exercised.

describe( 'Animation with SinonJs Fake Timer', function() { beforeEach( function() { this.clock = sinon.useFakeTimers(); this.$square = $( '<div class="moving-square"></div>' ); this.$square.appendTo( $( '#fixtures') ); playAnimation( this.$square ); }); afterEach( function() { $( '#fixtures' ).empty(); this.clock.restore(); }); it( 'should move 10 pixels per 20 milliseconds', function() { this.clock.tick( 20 ); expect( this.$square.css( 'left' ) ).to.equal( '10px' ); } ); it( 'should move to the right of the window @ speed 500px / second', function() { var maxX = $( 'body' ).width() - 50; var numOfSteps = Math.ceil( maxX / 10 ); var left = ( numOfSteps * 10 ) + 'px'; this.clock.tick( numOfSteps * 20 ); expect( this.$square.css( 'left' ) ).to.equal( left ); } ); it( 'should return to its original position @ speed 500px / second', function() { var maxX = $( 'body' ).width() - 50; var numOfSteps = Math.ceil( maxX / 10 ) * 2; this.clock.tick( numOfSteps * 20 ); expect( this.$square.css( 'left' ) ).to.equal( '0px' ); } ); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 describe ( 'Animation with SinonJs Fake Timer' , function ( ) { beforeEach ( function ( ) { this . clock = sinon . useFakeTimers ( ) ; this . $ square = $ ( '<div class="moving-square"></div>' ) ; this . $ square . appendTo ( $ ( '#fixtures' ) ) ; playAnimation ( this . $ square ) ; } ) ; afterEach ( function ( ) { $ ( '#fixtures' ) . empty ( ) ; this . clock . restore ( ) ; } ) ; it ( 'should move 10 pixels per 20 milliseconds' , function ( ) { this . clock . tick ( 20 ) ; expect ( this . $ square . css ( 'left' ) ) . to . equal ( '10px' ) ; } ) ; it ( 'should move to the right of the window @ speed 500px / second' , function ( ) { var maxX = $ ( 'body' ) . width ( ) - 50 ; var numOfSteps = Math . ceil ( maxX / 10 ) ; var left = ( numOfSteps * 10 ) + 'px' ; this . clock . tick ( numOfSteps * 20 ) ; expect ( this . $ square . css ( 'left' ) ) . to . equal ( left ) ; } ) ; it ( 'should return to its original position @ speed 500px / second' , function ( ) { var maxX = $ ( 'body' ) . width ( ) - 50 ; var numOfSteps = Math . ceil ( maxX / 10 ) * 2 ; this . clock . tick ( numOfSteps * 20 ) ; expect ( this . $ square . css ( 'left' ) ) . to . equal ( '0px' ) ; } ) ; } ) ;

While performing the animations may take even seconds, the three tests are executed in a fraction of a second. In fact, all 95 of our tests are executed in less than a second in the browser. Check it out on GitHub!

Summary

Fake the time for testing animations or any piece of functionality that is scheduled to be executed in the future. Take control of the timer, save its reference, start the piece of functionality containing setTimeout or setInterval and advance the timer forward. Verify the state of the application without waiting for the timing constraints in real time.

This was part seven of the test automation series on Mocha, Chai, and SinonJs. If you are interested in reading more on test automation, check out the complete series by clicking here. In the next article, we will set up a fake server with SinonJs.