DEV Community

Josh Branchaud
Josh Branchaud

Posted on

Test Timing-Based JS Functions with Jest

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);
};
Enter fullscreen mode Exit fullscreen mode

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();
  });
});
Enter fullscreen mode Exit fullscreen mode

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:

  1. Tell Jest to use fake timers for this test file.
  2. 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();
  });
});
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
danielcnascimento profile image
danielcnascimento

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.