Skip to main content
Bun provides a Jest-compatible mocking API. You can mock functions, spy on objects, and mock entire modules.

Mock functions

Create a mock function with mock():
import { test, expect, mock } from "bun:test";

test("mock function", () => {
  const mockFn = mock(() => "mocked");
  
  const result = mockFn();
  
  expect(result).toBe("mocked");
  expect(mockFn).toHaveBeenCalled();
  expect(mockFn).toHaveBeenCalledTimes(1);
});

Mock implementation

Control the return value of a mock:
import { test, expect, mock } from "bun:test";

test("mock implementation", () => {
  const mockFn = mock((a: number, b: number) => a + b);
  
  expect(mockFn(2, 3)).toBe(5);
  expect(mockFn).toHaveBeenCalledWith(2, 3);
});

Mock return values

Set return values without providing a full implementation:
import { test, expect, mock } from "bun:test";

test("mock return value", () => {
  const mockFn = mock();
  mockFn.mockReturnValue(42);
  
  expect(mockFn()).toBe(42);
  expect(mockFn()).toBe(42);
});

test("mock return value once", () => {
  const mockFn = mock();
  mockFn.mockReturnValueOnce(1).mockReturnValueOnce(2).mockReturnValue(3);
  
  expect(mockFn()).toBe(1);
  expect(mockFn()).toBe(2);
  expect(mockFn()).toBe(3);
  expect(mockFn()).toBe(3);
});

Mock resolved/rejected values

For async functions:
import { test, expect, mock } from "bun:test";

test("mock resolved value", async () => {
  const mockFn = mock();
  mockFn.mockResolvedValue("success");
  
  const result = await mockFn();
  expect(result).toBe("success");
});

test("mock rejected value", async () => {
  const mockFn = mock();
  mockFn.mockRejectedValue(new Error("failed"));
  
  await expect(mockFn()).rejects.toThrow("failed");
});

Mock implementation once

Provide different implementations for sequential calls:
import { test, expect, mock } from "bun:test";

test("mock implementation once", () => {
  const mockFn = mock();
  mockFn
    .mockImplementationOnce(() => "first")
    .mockImplementationOnce(() => "second")
    .mockImplementation(() => "default");
  
  expect(mockFn()).toBe("first");
  expect(mockFn()).toBe("second");
  expect(mockFn()).toBe("default");
  expect(mockFn()).toBe("default");
});

Spying on objects

Use spyOn() to spy on methods of an object:
import { test, expect, spyOn } from "bun:test";

test("spy on method", () => {
  const obj = {
    method: () => "original"
  };
  
  const spy = spyOn(obj, "method");
  
  obj.method();
  
  expect(spy).toHaveBeenCalled();
  expect(obj.method()).toBe("original"); // Original implementation preserved
});

Mock implementation

Override the implementation:
import { test, expect, spyOn } from "bun:test";

test("spy with mock implementation", () => {
  const obj = {
    getValue: () => "original"
  };
  
  const spy = spyOn(obj, "getValue").mockImplementation(() => "mocked");
  
  expect(obj.getValue()).toBe("mocked");
  expect(spy).toHaveBeenCalled();
});

Restore original implementation

Restore the original implementation:
import { test, expect, spyOn } from "bun:test";

test("restore spy", () => {
  const obj = {
    method: () => "original"
  };
  
  const spy = spyOn(obj, "method").mockReturnValue("mocked");
  
  expect(obj.method()).toBe("mocked");
  
  spy.mockRestore();
  
  expect(obj.method()).toBe("original");
});

Automatic cleanup with using

Bun supports automatic cleanup with the using keyword:
import { test, expect, spyOn } from "bun:test";

test("auto-restore with using", () => {
  const obj = {
    method: () => "original"
  };
  
  {
    using spy = spyOn(obj, "method").mockReturnValue("mocked");
    expect(obj.method()).toBe("mocked");
  } // spy.mockRestore() called automatically
  
  expect(obj.method()).toBe("original");
});

Mock matchers

Check how mock functions were called:
import { test, expect, mock } from "bun:test";

test("mock matchers", () => {
  const mockFn = mock();
  
  mockFn("hello", 42);
  mockFn("world", 100);
  
  // Called at all
  expect(mockFn).toHaveBeenCalled();
  
  // Called specific number of times
  expect(mockFn).toHaveBeenCalledTimes(2);
  
  // Called with specific arguments
  expect(mockFn).toHaveBeenCalledWith("hello", 42);
  expect(mockFn).toHaveBeenLastCalledWith("world", 100);
  expect(mockFn).toHaveBeenNthCalledWith(1, "hello", 42);
  expect(mockFn).toHaveBeenNthCalledWith(2, "world", 100);
});

Mock properties

Access mock call history:
import { test, expect, mock } from "bun:test";

test("mock properties", () => {
  const mockFn = mock((x: number) => x * 2);
  
  mockFn(5);
  mockFn(10);
  
  // Access call history
  expect(mockFn.mock.calls).toEqual([[5], [10]]);
  expect(mockFn.mock.results).toEqual([
    { type: "return", value: 10 },
    { type: "return", value: 20 }
  ]);
  
  // Call count
  expect(mockFn.mock.calls.length).toBe(2);
});

Clearing and resetting mocks

mockClear()

Clears call history but preserves mock implementation:
import { test, expect, mock } from "bun:test";

test("clear mock", () => {
  const mockFn = mock(() => "mocked");
  
  mockFn();
  expect(mockFn).toHaveBeenCalledTimes(1);
  
  mockFn.mockClear();
  
  expect(mockFn).toHaveBeenCalledTimes(0);
  expect(mockFn()).toBe("mocked"); // Implementation preserved
});

mockReset()

Clears call history and removes mock implementation:
import { test, expect, mock } from "bun:test";

test("reset mock", () => {
  const mockFn = mock(() => "mocked");
  
  mockFn();
  mockFn.mockReset();
  
  expect(mockFn).toHaveBeenCalledTimes(0);
  expect(mockFn()).toBeUndefined(); // Returns undefined
});

mockRestore()

Restores original implementation (for spies):
import { test, expect, spyOn } from "bun:test";

test("restore mock", () => {
  const obj = { method: () => "original" };
  const spy = spyOn(obj, "method").mockReturnValue("mocked");
  
  expect(obj.method()).toBe("mocked");
  
  spy.mockRestore();
  
  expect(obj.method()).toBe("original");
});

Module mocking

Mock entire modules with mock.module():
import { test, expect, mock } from "bun:test";

mock.module("./math.ts", () => ({
  add: mock((a: number, b: number) => 100), // Always returns 100
  subtract: mock((a: number, b: number) => 0)
}));

test("mocked module", async () => {
  const { add, subtract } = await import("./math.ts");
  
  expect(add(2, 3)).toBe(100);
  expect(subtract(5, 3)).toBe(0);
  
  expect(add).toHaveBeenCalledWith(2, 3);
});

Partial module mocking

Mock specific exports while keeping others:
import { mock } from "bun:test";

mock.module("./utils.ts", () => {
  const actual = require("./utils.ts");
  return {
    ...actual,
    dangerousFunction: mock(() => "safe")
  };
});

Global mock utilities

jest.clearAllMocks()

Clears all mocks:
import { test, expect, mock } from "bun:test";

test("clear all mocks", () => {
  const mock1 = mock();
  const mock2 = mock();
  
  mock1();
  mock2();
  
  jest.clearAllMocks();
  
  expect(mock1).toHaveBeenCalledTimes(0);
  expect(mock2).toHaveBeenCalledTimes(0);
});

jest.restoreAllMocks()

Restores all spies:
import { test, expect, spyOn } from "bun:test";

test("restore all mocks", () => {
  const obj1 = { method: () => "original1" };
  const obj2 = { method: () => "original2" };
  
  spyOn(obj1, "method").mockReturnValue("mocked1");
  spyOn(obj2, "method").mockReturnValue("mocked2");
  
  jest.restoreAllMocks();
  
  expect(obj1.method()).toBe("original1");
  expect(obj2.method()).toBe("original2");
});

Build docs developers (and LLMs) love