Fake Timers in Bun Test

Fake Timers in Bun Test

Published:

Bun test is not yet feature complete. But there is a mocking library, with which we can get at least fake timers running.

Cover image

Recently, I wrote the article, Is Bun Ready for Unit Testing? . In this article, I mentioned that Bun test currently does not implement the fake timers feature. This is a problem, if we have timers or intervals being used in our implementation code.

In Jest and Vitest, we have the fake timers, which enable us to execute timers in virtual time . So we don’t really have to waste time within our tests, waiting for the timer’s expiry, but we are precisely controlling when which scheduled timer’s callback functions are executed.

You can enable this feature via jest.useFakeTimers() or vi.useFakeTimers() .

How can we close the fake timers gap?

There is a standalone mocking library called SinonJS which provides some mocking capabilities for unit tests. Among those, there is also a fake timers implementation . We can install it via bun install -D @sinonjs/fake-timers @types/sinonjs__fake-timers to our project.

Here is an example test, using fake timers with vitest.

import {
  beforeEach,
  describe,
  expect,
  it,
  vi,
} from "vitest";
import { sleep } from "./sleep";

vi.useFakeTimers();

beforeEach(() => {
  vi.clearAllTimers();
  vi.setSystemTime(0);
});

describe("advanceTimersByTime()", () => {
  it("should advance timers", async () => {
    const sleeping = sleep(1000);

    vi.advanceTimersByTime(1000);
    await sleeping;

    expect(Date.now()).toBe(1000);
  }, 10);
});

Using Bun test with SinonJS fake timers, the example above could translate to this.

import { describe, expect, it } from "bun:test";
import { fakeTimers } from "../test/helpers/fake-timers";
import { sleep } from "./sleep";

const clock = fakeTimers();

describe("tick()", () => {
  it("should advance timers", async () => {
    const sleeping = sleep(1000);

    clock.tick(1000);
    await sleeping;

    expect(Date.now()).toBe(1000);
  }, 10);
});

Where the fakeTimers() function implementation looks like this:

import { install } from "@sinonjs/fake-timers";
import { afterAll, beforeEach } from "bun:test";

export function fakeTimers() {
  const clock = install();

  beforeEach(() => {
    clock.reset();
  });

  afterAll(() => {
    clock.uninstall();
  });

  return clock;
}

This solution ensures that the timers are cleaned up after the current test suite.

How far do we get with this solution?

Well, it seems to work in this simple example. But what about more complex tests? Are we safe to use SinonJS, or will we eventually hit major limitations?

SinonJS has been around for a long time, and it has a very complete time mocking API. It covers a good part of what we can test with Jest/Vitest fake timers.

Here is a list of Vitests fake timers functions and the SinonJS counterparts.

As we can see, the most important use cases are covered. I, personally, would not miss the missing features.

What are the pitfalls?

This all sounds very promising, but there are some pitfalls that we should be aware of.

  1. You may have noticed that we imported a custom sleep() implementation. It is based on the standard setTimeout() function. This is because SinonJS fake timers won’t work with Bun.sleep() and sleep() from the bun module.
  2. To make the migration of fake timers easy, when they eventually are implemented in Bun test, we should stay as close to the Jest/Vitest API as possible. There are some features in SinonJS fake timers that are not available in Jest/Vitest fake timers. E.g., specifying a duration for clock.tick() with a human-readable string or skipping registered timers with clock.jump() .

Conclusion

All in all, SinonJS can be a viable solution to the missing fake timers in Bun test. My only minor concern is that it comes with a restriction to our implementation code: We cannot use Bun.sleep() .

This is great news because there is one reason less that keeps us from using Bun test. When the test isolation, or at least the unmocking of mocked modules is implemented, I think Bun test finally becomes a serious alternative to the existing test runners.

Never stop learning and have a nice day!