DEV Community

Josh Branchaud
Josh Branchaud

Posted on

7

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.

Sentry blog image

How to reduce TTFB

In the past few years in the web dev world, we’ve seen a significant push towards rendering our websites on the server. Doing so is better for SEO and performs better on low-powered devices, but one thing we had to sacrifice is TTFB.

In this article, we’ll see how we can identify what makes our TTFB high so we can fix it.

Read more

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.

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay