JavaScript

Migrating from Jest to Bun's built-in test runner

JS

Bun's built-in test runner has matured over the past year to the point where it felt worth testing as a Jest replacement on an active project already running on the Bun runtime. We picked a mid-sized service with a few hundred test files as the pilot, reasoning that it was large enough to surface real migration friction but small enough that a rollback wouldn't be painful if the runner turned out not to be ready.

Test suite run time dropped dramatically, enough that our CI pipeline's test stage stopped being the slowest step in the pipeline, a distinction it had held for a long time. On our machines, the full suite went from a little over four minutes under Jest to under a minute with Bun's runner, and the gap was even more noticeable locally, where a single test file's feedback loop went from a couple of seconds to feeling nearly instant.

A handful of Jest-specific mocking patterns needed rewriting, but nothing that took more than a day across the whole suite. The trickiest cases involved module-level mocks that leaned on Jest's automocking behavior, which Bun's runner handles somewhat differently; we ended up making those mocks more explicit rather than relying on automatic behavior, which arguably left the tests more readable than before.

Snapshot testing worked with only minor adjustments to a couple of custom serializers, and our existing test file naming conventions carried over without any changes needed. The migration overall took about two days of focused work plus a few days of the whole team keeping an eye out for flaky-looking failures that turned out, in every case we checked, to be pre-existing flakiness the faster runner was simply surfacing sooner rather than new problems it introduced.

We wouldn't recommend migrating a test suite that leans heavily on Jest-specific ecosystem plugins without first checking compatibility, since not every third-party Jest plugin has an equivalent yet. For a suite using mostly core Jest APIs, though, the migration path is smooth enough that the speed gain alone makes it worth doing sooner rather than later.

Coverage reporting was the other area that needed a closer look before we trusted the migration. Bun's coverage output format differs from Jest's, and our CI pipeline had a threshold check wired directly into Jest's coverage JSON structure. Rewriting that check against Bun's format took less than an hour, but it's the kind of small compatibility gap that would have silently broken our coverage gating if we hadn't caught it during the migration itself rather than discovering it the next time coverage happened to dip.

Watch mode behaved differently enough to warrant a mention too. Jest's watch mode has years of accumulated heuristics for detecting which tests to re-run based on changed files, and Bun's implementation, while fast, occasionally re-ran a broader set of tests than Jest would have for the same change. In practice this just meant slightly longer watch-mode runs rather than any correctness issue, and given how much faster each individual run was, the net experience during local development was still a clear improvement over what we had before.

We're now defaulting to Bun's test runner for any new service built on the Bun runtime, and we've started a slower, lower-priority migration of a couple of older Jest suites on services that haven't yet moved to Bun as their runtime, mostly to capture the local development speed benefit even before those services make the larger runtime switch.

← Back to the journal

Have a project in mind?
Let’s talk.

Tell us where you are and where you want to go. We'll map the fastest route between the two.

Currently accepting new clients