Andrew Andrew - 9 months ago 47
Javascript Question

Spy on setTimeout and clearTimeout in Karma and Jasmine

I cannot seem to be able to spy on

in my Jasmine tests, which are being run through Karma.

I have tried variations on all of this

spyOn(window, 'setTimeout').and.callFake(()=>{});
spyOn(global, 'setTimeout').and.callFake(()=>{});
spyOn(window, 'clearTimeout').and.callThrough();

clock = jasmine.clock();
spyOn(clock, 'setTimeout').and.callThrough();


expect(window.setTimeout).toHaveBeenCalled(); // no
expect(global.setTimeout).toHaveBeenCalled(); // nope
expect(window.clearTimeout).toHaveBeenCalled(); // no again
expect(clock.setTimeout).toHaveBeenCalled(); // and no

In every case, I can confirm that
have been invoked in
, but instead I always get
Expected spy setTimeout to have been called.

, clearly this is because the test and the runner (the Karma window) are in different frames (so why should I expect anything different). But because of this, I can't see any way to confirm that these global functions have been invoked.

I know that I can use
to confirm that timeout/interval callbacks have been invoked, but it looks like I can't watch
itself. And confirming that
has been called simply isn't possible.

At this point, the only thing I can think of is to add a separate layer of abstraction to wrap
or to inject the functions as dependencies, which I've done before, but I think is weird.

Answer Source

The only -- and only -- solution I could find for this is to use Rewire (in my case, I am required to also use Rewire-Webpack).

Rewire does allow you to replace global methods -- but once the method has been replaced, it cannot be spied upon. So, to actually to successfully use toHaveBeenCalledWith, you must wrap and proxy the mock function.

var rewire = require('rewire'),
    myModule = rewire('./path/to/module');

describe(function () {
    var mocks = {
        setTimeout: function () { return 99: },
        clearTimeout: function () {}

    beforeEach(function () {
        // This will work
        myModule.__set__('setTimeout', function () {
            mocks.setTimeout.apply(null, arguments)

        // This will NOT work
        myModule.__set__('clearTimeout', mocks.clearTimeout)

    it('calls setTimeout', function () {
        spyOn(mocks, 'setTimeout').and.callThrough();
        spyOn(mocks, 'clearTimeout').and.callThrough();

        myModule.doSomething(); // this will invoke setTimeout locally

        expect(mocks.setTimeout).toHaveBeenCalledWith(jasmine.any(Function), 1000);
        expect(mocks.clearTimeout).toHaveBeenCalledWith(99); // Won't work (see above)


Naturally, this will surely stop working the next time Jasmine, Rewire, Karma, Webpack... or the weather... changes (grrr). If this doesn't work for you, please leave a comment so future devs will know.