Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bluebird promises freeze when using Sinon's fake timer

The following test freezes when used with Sinon's fake timers and Bluebird.

var sinon = require('sinon');
var Promise = require('bluebird');

describe('failing test', function() {
  beforeEach(function() {
    this.clock = sinon.useFakeTimers();
  });
  afterEach(function() {
    this.clock.restore();
  });
  it('test', function(done) {
    Promise.delay(1000).then(function(){
        done(); //This never gets called     
    });
  });
});

I am using Mocha (v2.2.5) with Bluebird (v2.9.33) and Sinon (v1.15.3).

I tried the suggestions offered in some discussions in Bluebird and Sinon but I couldn't make this work. This seems to be an issue with the way Sinon stubs setImmediate but other than that I have no clue how to resolve this.

like image 863
George Eracleous Avatar asked Jul 14 '15 10:07

George Eracleous


2 Answers

You need to step the fake timer manually like so:

describe('failing test', function() {
  it('test', function(done) {
    Promise.delay(1000).then(function(){
        done(); //This never gets called     
    });
    //
    // ADVANCE THE CLOCK:
    //
    this.clock.tick(1000);
  });
});

Btw, mocha has built-in support for promises, so an even better way to do this would be to return your promise:

describe('failing test', function() {
  it('test', function() { // No more "done" callback
    var p = Promise.delay(1000).then(function(){
        console.log('done'); 
    });
    this.clock.tick(1000);
    return p; // Return the promise instead - mocha will take of the async magic goodness!
  });
});

From my experience, mixing promises and the done callback style leads to all sorts of trouble and hard to track errors. When using promises, try to stick with returning, and have a look at a library such as chai-as-promised. I promise you it'll make your tests much more readable!

like image 143
BadIdeaException Avatar answered Oct 14 '22 18:10

BadIdeaException


Best practices:

Don't use fake timers, they can cause weird concurrency issues because they synchronously call deferred functions - thus changing execution. Instead use Mocha's built in promises support as:

describe('failing test', function() {
    it('test', function(){ 
        return Promise.delay(1000); // return the promise here, no `done`
    });

But if you must

Please don't, but you can tell bluebird to run its then callbacks synchronously, I strongly recommend against this and it will cause timing issues in your app:

Promise.setScheduler(function(fn){
    return fn();
});
like image 35
Benjamin Gruenbaum Avatar answered Oct 14 '22 16:10

Benjamin Gruenbaum