Monday, May 20, 2024
112
rated 0 times [  119] [ 7]  / answers: 1 / hits: 19950  / 9 Years ago, fri, january 29, 2016, 12:00:00

I cannot seem to be able to spy on setTimeout and clearTimeout 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();
clock.install();
spyOn(clock, 'setTimeout').and.callThrough();

runMyCode();

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 setTimeout and clearTimeout have been invoked in runMyCode, but instead I always get Expected spy setTimeout to have been called.



For window, 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 jasmine.clock() to confirm that timeout/interval callbacks have been invoked, but it looks like I can't watch setTimeout itself. And confirming that clearTimeout 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 setTimeout and clearTimeout or to inject the functions as dependencies, which I've done before, but I think is weird.


More From » unit-testing

 Answers
3

Edit: Since asking this question, it looks like Jasmine has implemented Clock, which makes this kind of mocking possible. And, as Piotr Jaworski's answer points out, Facebook's Jasmine-based Jest provides its own (arguably much better) way of mocking and spying on timed tasks.



So, the rest of the answer is dated....



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.


[#63518] Wednesday, January 27, 2016, 9 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
margaritakristinak

Total Points: 502
Total Questions: 127
Total Answers: 98

Location: England
Member since Mon, May 17, 2021
3 Years ago
;