Sinon: useFakeTimers does not work with lodash because of cached copies

Created on 8 Nov 2013  路  11Comments  路  Source: sinonjs/sinon

Related: https://github.com/lodash/lodash/issues/304

lodash provides a _.runInContext to allow configuration of the context, but I have not been able to get it to work with fakeTimers. If anyone could get this to work, it would be great if sinon was able to handle this case so that others don't get stuck on this problem.

Most helpful comment

Whoa! It's like @jdalton is watching us - he just added support for this in (as-yet-unreleased) https://github.com/lodash/lodash/commit/14845416e3986201d28d01195484770429b7d56d

I pulled master and did a quick test - this works like a charm!

  it('can stub clock for debounce', function () {
    var clock = lolex.install();
    var spy = sinon.spy();
    _.debounce(spy, 1000)();
    expect(spy.callCount).to.equal(0);
    clock.tick(1);
    expect(spy.callCount).to.equal(0);
    clock.tick(998);
    expect(spy.callCount).to.equal(0);
    clock.tick(1);
    expect(spy.callCount).to.equal(1);
    clock.uninstall();
  });

Thanks @jdalton!

All 11 comments

+1

I don't understand what you want from Sinon?

I guess I'm hoping that, if lodash is included in the evironment, the lodash context could be set up in such a way that the timers cached and used by lodash could be stubbed as expected when using fake timers. If this wasn't done by sinon, it seems like useFakeTimers would need to be reimplemented specifically for lodash.

I still don't quite understand what you want. If there are generic tweaks that can be made to Sinon to make it play nicer with other libraries like lodash, please suggest something concrete. If there's a need for lodash-specific code, it sounds like someone should spin up a sinon-lodash helper library ;)

On this subject, lodash author recently (may 2016) made a gracious move of no longer caching some functions :

There is still an issue with Date.now(), which may be solved from sinon/lolex size. I'm working on it.

Now that lolex uses now = function() {return Date.now}, why is there still an issue?

I think it's because the main lodash export calls runInContext which closes over a bunch of globals including Date. Even after lolex replaces window.Date, lodash is referring to the version it closed over.

You could solve this by installing lolex once before requiring any production code, but at least in the tests I'm currently working on that's not a great solution; I'd rather install lolex before each test and uninstall it afterward. You could also use runInContext directly and inject a modified lodash into everything under test, but I'm not a fan of that either. If someone has a better solution I'm all ears.

I wonder why lodash closes over Date but not setTimeout?

setTimeout = function(func, wait) { return context.setTimeout.call(root, func, wait); };

vs

function now() {
  return Date.now(); // mocking would work with `context.Date.now()`
}

I'm not sure; the only thing I can guess from the comments is because it's a constructor, but that shouldn't make a difference here (and new Date() is never called within lodash as far as I can tell).

Actually, now() is the only usage of the closed-over Date and there are only four usages of now(). This seems like a straightforward change. Shall we propose it over at lodash/lodash?

Whoa! It's like @jdalton is watching us - he just added support for this in (as-yet-unreleased) https://github.com/lodash/lodash/commit/14845416e3986201d28d01195484770429b7d56d

I pulled master and did a quick test - this works like a charm!

  it('can stub clock for debounce', function () {
    var clock = lolex.install();
    var spy = sinon.spy();
    _.debounce(spy, 1000)();
    expect(spy.callCount).to.equal(0);
    clock.tick(1);
    expect(spy.callCount).to.equal(0);
    clock.tick(998);
    expect(spy.callCount).to.equal(0);
    clock.tick(1);
    expect(spy.callCount).to.equal(1);
    clock.uninstall();
  });

Thanks @jdalton!

Thanks @jdalton! Thanks +10000

Was this page helpful?
0 / 5 - 0 ratings