fake-timers: Problem with useFakeTimers: Promise are freeze when resolve it from setTimeout

From @Qoter on May 5, 2017 13:30

  • Sinon version : 2.2.0
  • Environment : Node.js v6.10.2
  • Other libraries: mocha 3.3.0

Code example

it('promise freeze', () => {
    let clock = sinon.useFakeTimers();
    let p = Promise.resolve()
        .then(() => new Promise(resolve => setTimeout(resolve, 5)));
    clock.tick(1000);
    return p;
});

I expect that test will pass.
But test fail with timeout and promise are frozen.
Without sinon.useFakeTimers() this test will pass.

Copied from original issue: sinonjs/sinon#1397

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 19 (15 by maintainers)

Most upvoted comments

This is expected behaviour and how promises behave.

The clock.tick will execute before the setTimeout call because promises always defer a microtask. That’s the issue - the reason the test “succeeds” without lolex is that 5ms actually pass and the test is async.

There is nothing we can do about this at this point. On the other hand I’ve been talking with the V8 team about exposing a flag (they already have) that lets you flush the promise microtick queue which would be great - they’re working on it 😃

@rgomezp I just tried manually seeing if it was there. I grepped for tick, promise, flush by doing node --v8-options | grep flush, and I could not see anything that related to it. So it does not seem to be there.

I am quite sure you are able to find the Node developers mailing list or similar if you search for it, but not sure that is the quickest route to your goal. If you want easier testing, you could consider using a promise library in between, which is how guess Angular’s flushMicrotasks achieves this.

Thank @fatso83 for moving this. And many thanks to you @benjamingr, for explaining exactly what is going on.

Thanks, benji. Didn’t use much time looking into the specifics, just saw it lingering on the main Sinon project and thought it should be moved here.

From @lucasfcosta on May 6, 2017 18:38

Hi @Qoter, thanks for your issue 😄

I believe this happens because you either gotta use a timer from the clock variable you assigned useFakeTimers to or you need to call the install method.

This is one of the possible fixes for your test:

let clock = fakeTimers.useFakeTimers();
let p = Promise.resolve()
  .then(() => new Promise(resolve => fakeTimers.timers.setTimeout(resolve, 5)));
clock.tick(1000);
return p;

The other one, using install to replace the global setTimeout function can be used by installing the lolex module, as you can see in this section at Lolex docs.