Asynchronous code is a key part of writing JavaScript. Modern web apps require timing-based elements like setTimeout
and setInterval
. For instance, a loading indicator that displays only after 100ms have elapsed or a debounced API endpoint that ensures requests aren't fired too often.
But, how do we test this kind of timing-based functionality?
I dig into this question in a quick screencast. Give it a watch!
Details
Jest offers a set of Fake Timer utilities that can be used to test functions that rely on functions like setTimeout
and setInterval
.
Here is a basic delay
function that uses setTimeout
:
export const delay = (milliseconds, fn) => {
setTimeout(() => {
fn();
}, milliseconds);
};
We might take a first stab at testing delay
with something like this:
import { delay } from "./delay";
describe("delay", () => {
test("does something after 200ms", () => {
const doSomething = jest.fn();
delay(200, doSomething);
expect(doSomething).toHaveBeenCalled();
});
});
Because of the setTimeout
, the expect
call will be evaluated as a failure before delay
is able to fire doSomething
.
Not only does JavaScript not offer a clean way to sleep, but we wouldn't want to slow our test suite down with a bunch of sleep-like calls anyway.
Instead, we can take advantage of the timer mocks that Jest offers.
This only requires two additional lines of code:
- Tell Jest to use fake timers for this test file.
- Tell Jest to advance timers by enough for the
setTimeout
to be triggered.
import { delay } from "./delay";
jest.useFakeTimers();
describe("delay", () => {
test("does something after 200ms", () => {
const doSomething = jest.fn();
delay(200, doSomething);
jest.advanceTimersByTime(200);
expect(doSomething).toHaveBeenCalled();
});
});
Advancing the timers by 200
milliseconds causes the setTimeout
to call doSomething
which our test's expectation can verify.
You can even use multiple jest.advanceTimersByTime
calls in a test if your situation calls for it.
There is more where that came from. If you enjoyed this post and screencast, subscribe to my newsletter and check out more of my screencasts.
Top comments (1)
I tried to use setTimeout to delay 2 seconds (longer than necessary) to render the lazy loaded (code splitting from react) component to the DOM, but failed, just that "Loading..." fallback is being shown.