loading...

Test Timing-Based JS Functions with Jest

jbranchaud profile image Josh Branchaud ・2 min read

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:

  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();
  });
});

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.

Posted on by:

jbranchaud profile

Josh Branchaud

@jbranchaud

I'm a developer and consultant focused primarily on the web, specializing in React, Ruby on Rails, and PostgreSQL. Newsletter: https://tinyletter.com/jbranchaud

Discussion

markdown guide