Search code examples
javascriptjestjssinonclock

Understanding Sinon clock `tick` vs `tickAsync`


I am trying to understand the difference between clock.tick and clock.tickAsync and where should I use each one. The explanation in the docs is not clear to me, and by looking at the code itself I still can't understand where and when should I use each method. Can anyone help me with a concrete code example?


Solution

  • The tickAsync() will also break the event loop, allowing any scheduled promise callbacks to execute before running the timers.

    First, I recommend looking the video about event loop.

    If short, the difference between tick and tickAsync is that tick - is call timers synchronous and tickAsync is - call timers asynchronous that allow to execute resolved promises before calling timers

    What synchronous means. Let's look at the example code which we would test

    function myFunction() {
      setTimeout(() => console.log('foo'), 1000)
      Promise.resolve().then(() => console.log('zoo'));
      console.log('bar')
    }
    

    and our synchronous test code

    it('should test synchronously', () => {
       myFunction()
       tick(1000); // This would call scheduled timers synchronously ⚠️
       console.log('baz')
    });
    // print 'bar'
    // print 'foo'
    // print 'baz'
    // print 'zoo'
    

    and now let's see the same test code but with tickAsync

    it('should test synchronously', () => {
       myFunction()
       tickAsync(1000);
       console.log('baz')
    });
    // print 'bar'
    // print 'baz' 👈
    // print 'zoo' 👈
    // print 'foo' 👈
    

    As you can see, the order of printed string is changed. Now the same code but with await

    it('should test synchronously', async () => {
       myFunction()
       await tickAsync(1000);
       console.log('baz')
    });
    // print 'bar'
    // print 'zoo' 👈
    // print 'foo' 👈
    // print 'baz' 👈
    

    To understand why the order changed, you should understand the difference betweens micro tasks queue and regular queue. But if you watched the video from link above, you already should get the idea 😊

    P.S. Also, you should understand that sinon.useFakeTimers(); will replace setTimeout and setInterval on own implementation that allow sinon to have full control over your timers passed to these functions

    This is a very rough approximation how sinon tick & tickAsync implemented

    scheduledTimers = [];
    
    function useFakeTimers() {
        global.setInterval = global.setTimeout = (fn) => {
            scheduledTimers.push(fn);
        }
    }
    
    function tick() {
        while (scheduledTimers.length) {
            scheduledTimers.shift()();
        }
    }
    
    function tickAsync() {
        // Call timers on the next tick
        return Promise.resolve().then(() => tick());
    }