Spy on setTimeout and clearTimeout in Karma and Jasmine
Solution 1
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.
Solution 2
I was able to get it to work like this:
spyOn(window, 'setTimeout');
runMyCode();
expect(setTimeout).toHaveBeenCalled();
Just remove the 'window' object from the setTimeout call.
Solution 3
For those looking for a Jest solution, it has dedicated fake timer functions (which are also spyable).
Andrew
Since 1998, when I began my professional career in programming and web development, and while working for some of Canada's top media companies,, I've developed the following skills: Team management Site architecture hybrid mobile development (iPhone & Android -- plus BlackBerry while it lasted) SQL (MS SQL Server, MySQL, SQLite) JavaScript (my current focus is React + vanilla, but past work includes Angular, Backbone and jQuery) Node.js .NET & C# Objective C Java (Android) PHP I've also done a good deal of professional work in the past in: Adobe Flash (since version 3.0) ActionScript (original, 2.0 and 3.0) Adobe Air Perl ColdFusion WebTV
Updated on June 21, 2022Comments
-
Andrew almost 2 years
I cannot seem to be able to spy on
setTimeout
andclearTimeout
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
andclearTimeout
have been invoked inrunMyCode
, but instead I always getExpected 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 watchsetTimeout
itself. And confirming thatclearTimeout
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
andclearTimeout
or to inject the functions as dependencies, which I've done before, but I think is weird. -
Andrew almost 6 yearsSince asking this question, I've switched to Jest specifically because of how much easier mocking/stubbing is. However, I should also say that in the same time, Jasmine has also implemented its own version, in Clock. The interface is very much different and looks a bit harder to use than Jest.