testing v1.0.0 · Updated Apr 17, 2026 · by Justin Adams

Ban mock.module in Bun Tests

Stops Claude Code from reaching for Bun's mock.module — which poisons the global module registry and breaks test isolation. Forces factory + dependency injection patterns instead.

Claude Code Cursor Codex
$ curl -fsSL https://www.cendis.ai/library/skill/no-mock-module/install | sh

What it does

Prevents the agent from using Bun’s mock.module API in tests. mock.module mutates the global module registry for the entire test process — once one test mocks node:fs, every subsequent test sees the mock until the process exits. This causes flaky CI, order-dependent failures, and “works locally” bugs that are nightmares to debug.

The skill teaches the agent to refactor toward factory functions and dependency injection instead, which keep mocks scoped to a single test.

When to use it

  • Any Bun project with a non-trivial test suite
  • After you’ve been bitten by a CI failure that disappeared when tests ran in isolation
  • During code review of agent-generated tests
  • When migrating from Jest (where jest.mock is well-isolated) to Bun

How it works

Add to your CLAUDE.md:

## Testing — No mock.module

Bun's `mock.module` poisons the module registry globally and has caused
repeated CI failures. Use factory + dependency injection patterns instead.

If you encounter existing `mock.module` usage while working on adjacent code,
flag it as a refactor candidate rather than extending the pattern.

The pattern to use instead

Bad — uses mock.module (poisons global registry):

import { mock } from "bun:test";

mock.module("./email-client", () => ({
  sendEmail: () => Promise.resolve({ id: "fake" }),
}));

import { signupHandler } from "./signup";

test("signup sends welcome email", async () => {
  await signupHandler({ email: "test@example.com" });
  // mock leaks into every other test in the process
});

Good — factory + DI (mock scoped to one test):

// signup.ts
export function makeSignupHandler(deps: { sendEmail: SendEmail }) {
  return async (input: SignupInput) => {
    // ...
    await deps.sendEmail({ to: input.email, template: "welcome" });
  };
}

// signup.test.ts
test("signup sends welcome email", async () => {
  const sendEmail = mock(() => Promise.resolve({ id: "fake" }));
  const handler = makeSignupHandler({ sendEmail });

  await handler({ email: "test@example.com" });
  expect(sendEmail).toHaveBeenCalledWith({
    to: "test@example.com",
    template: "welcome",
  });
});

Why it matters

Mock isolation is a property of the test framework. Bun chose to make mock.module process-global rather than file-scoped, which is a footgun for any non-trivial codebase. Factory + DI is more verbose at the call site, but it makes dependencies explicit, keeps mocks scoped, and works in any test runner — including future migrations away from Bun.