A coworker at work is increasing the codebase unit test coverage. So, because I am one of the primary reviewer of his work, I am reading more about unit tests and I have learned something interesting about Bun’s test runner.
The Problem
Tests passed individually but failed randomly when run together with randomized order (bun test --randomize --seed=1).
The Cause
Bun’s mock.module(...) is process-global. When multiple test files shared the same mock dependencies at the top level, randomization creates race conditions. The first file loaded dictates the mock state for subsequent files, causing unpredictable failures because the files mocked overlapping module IDs. So, whichever file is loaded first could affect the other file’s imports/mocks.
Example
For example, suppose two test files mock the same module:
// test/route.test.ts
import { mock } from "bun:test";
mock.module("route", () => ({ default: { get: () => "route" } }));
// test/server.test.ts
import { mock } from "bun:test";
mock.module("route", () => ({ default: { get: () => "server" } }));If route.test.ts loads first, all tests using route will return "route", even in server.test.ts. Randomizing the order will show you this problem.
The Solution
To fix the problem, you need to isolate mocks and imports per test.
- Move
mock.module(...)calls insidebeforeEach - Call
mock.restore()at the start and end of each test cycle to reset state to avoid contamination - Re-import the module under test inside
beforeEachwith a unique query suffix (for example, something like a counter?test=${moduleCount++}). So, this will forces each test to get a fresh module initialization and prevents cached side effects. Without the suffix, tests may still pass in stable order, but that can hide order/cache-related flakiness. This makes runs deterministic, including randomized seeds.
Example
Here is an example of isolate mocks and imports per test:
// test/route.test.ts
import { mock } from "bun:test";
let moduleCount = 0;
beforeEach(() => {
mock.restore();
mock.module("route", () => ({ default: { get: () => "route" } }));
const module = await import(`./route?test=${moduleCount++}`);
});
afterEach(() => {
mock.restore();
});Now, each test gets a fresh mock and module instance, and fixing the problem of cross-file leakage.
Bottom Line
Treat top-level mock.module(...) as global state if module IDs overlap across tests. To avoid leakage, use the isolation pattern:
- Set up
mock.modules(...)inbeforeEach. - Call
mock.restore()at at test boundaries. - Import the module under test after mocking, using a unique suffix to bypass cache contamination.
Reference:
If you want to go deeper, I redirect you to Bun’s documentation on mocks: https://bun.com/docs/test/mocks