James A. Rosen
James A. Rosen

Reputation: 65262

How do I test Ember.run.later with Sinon?

I have some Ember code that sets a timeout:

var MyObject = Ember.Object.extend({
  setFooToBarLater: function() {
    Ember.run.later(this, 'set', 'foo', 'bar', 500);
  }
});

I'd like to test that using Sinon's fake clock. Here's what I tried:

var clock = sinon.useFakeTimers();
var myObject = MyObject.create();
myObject.setFooToBarLater();
clock.tick(600);
expect(myObject.get('foo')).to.be('bar');

But the expect always runs before the set. I also tried wrapping the clock.tick in a run-loop:

Ember.run(clock, 'tick', 600);

Upvotes: 5

Views: 2122

Answers (3)

morhook
morhook

Reputation: 1269

With ember 2.4.3 and sinon 0.5.0 seems to be fixed this kind of problems. sinon.useFakeTimers() works as expected with similar code on unit test:

const clock = sinon.useFakeTimers();
assert.expect(1);
var MyObject = Ember.Object.extend({
    setFooToBarLater: function() {
      Ember.run.later(this, 'set', 'foo', 'bar', 500);
    }
  }), myObject = MyObject.create();
myObject.setFooToBarLater();
clock.tick(600);
assert.equal(myObject.get('foo'), 'bar');
clock.restore();

Upvotes: 1

opichals
opichals

Reputation: 313

Beware that sinon.useFakeTimers (by default on) overrides the window.Date. That becomes an issue once you already have a scheduled Ember runloop and the useFakeTimers() is turned on. This is because Backburner (the Ember.js runloop implementation) setTimeout /core scheduling method/ tries hard to optimize calling window.setTimeout (look for executeAt in ember.js)

After going through the sinon source I made the following mod to let those two get along nicely (gist). It instructs sinon not to touch the window.Date and also patches sinon.test() to wait few more ticks after the test body which allows scheduled timers to get executed and async codes

sinon._originalUseFakeTimers = sinon.useFakeTimers;
sinon.useFakeTimers = function() {
    // make sure we don't override window.Date used in
    // Backburner.setTimeout() to optimize window.setTimeout() call numbers
    return sinon._originalUseFakeTimers.apply(this, [new Date().getTime(), "setTimeout", "setInterval", "clearTimeout", "clearInterval"]);
};

sinon._originalTest = sinon.test;
sinon.test = function(callback) {
    return sinon._originalTest.call(this, function() {
        callback.apply(this, arguments);

        // wait for further runloops to finish (2s is the mocha timeout)
        this.clock.tick(2000);
    });
};

Upvotes: 4

James A. Rosen
James A. Rosen

Reputation: 65262

The answer appears to be not putting the clock.tick in a run-loop, but the call that actually invokes Ember.run.later. Thus, the following does work:

var clock = sinon.useFakeTimers();
var myObject = MyObject.create();
Ember.run(myObject, 'setFooToBarLater');
clock.tick(600);
expect(myObject.get('foo')).to.be('bar');

Upvotes: 3

Related Questions