> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/oven-sh/bun/llms.txt
> Use this file to discover all available pages before exploring further.

# Mocks & Spies

> Create mock functions, spy on existing methods, and replace entire modules using bun:test's mocking API.

Bun provides comprehensive mocking capabilities through `bun:test`, including function mocks, spies, and module mocks. The API is fully compatible with Jest's mocking interface.

## Creating mock functions

Use `mock()` to create a tracked function with a controllable implementation:

```typescript theme={null}
import { mock, test, expect } from "bun:test";

const fetchUser = mock(() => Promise.resolve({ id: 1, name: "Alice" }));

test("fetches user", async () => {
  const user = await fetchUser(1);
  expect(user.name).toBe("Alice");
  expect(fetchUser).toHaveBeenCalledTimes(1);
  expect(fetchUser).toHaveBeenCalledWith(1);
});
```

### jest.fn() compatibility

`jest.fn()` is an alias for `mock()` and behaves identically:

```typescript theme={null}
import { test, expect, jest } from "bun:test";

const random = jest.fn(() => Math.random());

test("jest.fn works", () => {
  random();
  expect(random).toHaveBeenCalled();
});
```

### vi compatibility

For tests migrated from Vitest, `vi.fn()` is also available:

```typescript theme={null}
import { test, expect, vi } from "bun:test";

const mockFn = vi.fn(() => 42);
mockFn();
expect(mockFn).toHaveBeenCalled();
```

## Mock call history

Every mock records its call arguments and return values:

```typescript theme={null}
import { mock } from "bun:test";

const multiply = mock((x: number) => x * 2);

multiply(5);
multiply(10);

multiply.mock.calls;
// [[5], [10]]

multiply.mock.results;
// [{ type: "return", value: 10 }, { type: "return", value: 20 }]
```

## Controlling return values

<Tabs>
  <Tab title="mockReturnValue">
    Set a fixed return value for all subsequent calls:

    ```typescript theme={null}
    const getStatus = mock(() => "online");
    getStatus.mockReturnValue("offline");

    expect(getStatus()).toBe("offline");
    ```
  </Tab>

  <Tab title="mockReturnValueOnce">
    Set a return value for the next call only:

    ```typescript theme={null}
    const roll = mock(() => 1);
    roll.mockReturnValueOnce(6);
    roll.mockReturnValueOnce(4);

    expect(roll()).toBe(6);
    expect(roll()).toBe(4);
    expect(roll()).toBe(1); // falls back to original
    ```
  </Tab>

  <Tab title="mockResolvedValue">
    Set a resolved Promise value (for async mocks):

    ```typescript theme={null}
    const fetchProfile = mock(async () => ({ name: "Alice" }));
    fetchProfile.mockResolvedValue({ name: "Bob" });

    const user = await fetchProfile();
    expect(user.name).toBe("Bob");
    ```
  </Tab>

  <Tab title="mockRejectedValue">
    Make the mock reject with an error:

    ```typescript theme={null}
    const fetchData = mock(async () => ({}));
    fetchData.mockRejectedValue(new Error("Network error"));

    await expect(fetchData()).rejects.toThrow("Network error");
    ```
  </Tab>
</Tabs>

## Controlling implementations

### mockImplementation

Replace the mock's entire implementation:

```typescript theme={null}
const process = mock((x: number) => x);
process.mockImplementation(x => x * 100);

expect(process(5)).toBe(500);
```

### mockImplementationOnce

Override the implementation for only the next call:

```typescript theme={null}
const fn = mock(() => "default");
fn.mockImplementationOnce(() => "first");
fn.mockImplementationOnce(() => "second");

expect(fn()).toBe("first");
expect(fn()).toBe("second");
expect(fn()).toBe("default");
```

## Mock properties and methods reference

| Property / Method                         | Description                                    |
| ----------------------------------------- | ---------------------------------------------- |
| `mockFn.mock.calls`                       | Array of arguments for each invocation         |
| `mockFn.mock.results`                     | Array of return values for each invocation     |
| `mockFn.mock.instances`                   | Array of `this` contexts for each invocation   |
| `mockFn.mock.lastCall`                    | Arguments of the most recent call              |
| `mockFn.mockClear()`                      | Clears call history, keeps implementation      |
| `mockFn.mockReset()`                      | Clears call history and removes implementation |
| `mockFn.mockRestore()`                    | Restores the original implementation           |
| `mockFn.mockImplementation(fn)`           | Sets a new implementation                      |
| `mockFn.mockImplementationOnce(fn)`       | Sets implementation for the next call only     |
| `mockFn.mockReturnValue(value)`           | Sets a fixed return value                      |
| `mockFn.mockReturnValueOnce(value)`       | Sets return value for the next call only       |
| `mockFn.mockResolvedValue(value)`         | Sets a resolved Promise value                  |
| `mockFn.mockResolvedValueOnce(value)`     | Sets resolved Promise for the next call only   |
| `mockFn.mockRejectedValue(value)`         | Sets a rejected Promise value                  |
| `mockFn.mockRejectedValueOnce(value)`     | Sets rejected Promise for the next call only   |
| `mockFn.mockReturnThis()`                 | Returns `this` from the mock                   |
| `mockFn.withImplementation(fn, callback)` | Temporarily replaces implementation            |
| `mockFn.getMockName()`                    | Returns the mock's name                        |
| `mockFn.mockName(name)`                   | Sets the mock's name                           |

## Spying on existing methods

Use `spyOn()` to wrap an existing method with tracking without replacing it by default:

```typescript theme={null}
import { test, expect, spyOn } from "bun:test";

const ringo = {
  name: "Ringo",
  sayHi() {
    return `Hi, I'm ${this.name}`;
  },
};

const spy = spyOn(ringo, "sayHi");

test("tracks calls without replacing", () => {
  expect(spy).toHaveBeenCalledTimes(0);
  ringo.sayHi();
  expect(spy).toHaveBeenCalledTimes(1);
});
```

Spies support the same `mockImplementation`, `mockReturnValue`, etc. methods as regular mocks:

```typescript theme={null}
const spy = spyOn(userService, "getUser").mockResolvedValue({
  id: "1",
  name: "Mocked User",
});

const result = await userService.getUser("1");
expect(result.name).toBe("Mocked User");
expect(spy).toHaveBeenCalledWith("1");
```

## Module mocking

Use `mock.module()` to replace an entire module with a controlled implementation. Both `import` and `require` are supported.

```typescript theme={null}
import { test, expect, mock } from "bun:test";

mock.module("./config", () => ({
  apiUrl: "http://localhost:3001",
  timeout: 1000,
}));

test("uses mocked config", async () => {
  const config = await import("./config");
  expect(config.apiUrl).toBe("http://localhost:3001");
});
```

### Mocking external packages

```typescript theme={null}
mock.module("pg", () => ({
  Client: mock(function () {
    return {
      connect: mock(async () => {}),
      query: mock(async () => ({ rows: [{ id: 1, name: "Test User" }] })),
      end: mock(async () => {}),
    };
  }),
}));
```

### Live bindings

ESM modules maintain live bindings. Updating the mock after an import is already in effect will update all existing references:

```typescript theme={null}
import { foo } from "./module";

test("live binding update", async () => {
  expect(foo).toBe("original");

  mock.module("./module", () => ({ foo: "mocked" }));

  // Live binding is updated
  expect(foo).toBe("mocked");
});
```

### Preloading module mocks

To prevent the original module from executing at all, register mocks in a preload file:

```typescript my-preload.ts theme={null}
import { mock } from "bun:test";

mock.module("./api-client", () => ({
  fetchUser: mock(async (id: string) => ({ id, name: "Test User" })),
}));
```

```toml bunfig.toml theme={null}
[test]
preload = ["./my-preload.ts"]
```

## Global mock management

### mock.restore()

Restore all mock functions to their original implementations at once. Useful in `afterEach`:

```typescript theme={null}
import { afterEach, mock } from "bun:test";

afterEach(() => {
  mock.restore();
});
```

<Note>
  `mock.restore()` does not reset modules overridden with `mock.module()`.
</Note>

### mock.clearAllMocks()

Reset call history for all mocks without restoring implementations:

```typescript theme={null}
import { mock } from "bun:test";

const a = mock(() => 1);
const b = mock(() => 2);

a();
b();

mock.clearAllMocks();

expect(a).toHaveBeenCalledTimes(0);
expect(b).toHaveBeenCalledTimes(0);
// Implementations are preserved
expect(a()).toBe(1);
```

## Best practices

<AccordionGroup>
  <Accordion title="Keep mocks simple">
    Mocks with complex internal logic are hard to maintain and can introduce bugs of their own. Return the minimum data your test needs.

    ```typescript theme={null}
    // Good
    const mockApi = {
      getUser: mock(async (id: string) => ({ id, name: "Test User" })),
    };
    ```
  </Accordion>

  <Accordion title="Restore mocks after each test">
    Always clean up to prevent test pollution:

    ```typescript theme={null}
    import { afterEach, mock } from "bun:test";

    afterEach(() => {
      mock.restore();
      mock.clearAllMocks();
    });
    ```
  </Accordion>

  <Accordion title="Use type-safe mocks">
    ```typescript theme={null}
    interface UserService {
      getUser(id: string): Promise<User>;
    }

    const mockUserService: UserService = {
      getUser: mock(async (id: string) => ({ id, name: "Test User" })),
    };
    ```
  </Accordion>

  <Accordion title="Verify mock interactions">
    ```typescript theme={null}
    test("service calls API with correct arguments", async () => {
      const mockFetch = mock(async () => ({ id: "1" }));
      const service = new UserService({ fetch: mockFetch });

      await service.getUser("123");

      expect(mockFetch).toHaveBeenCalledWith("123");
      expect(mockFetch).toHaveBeenCalledTimes(1);
    });
    ```
  </Accordion>
</AccordionGroup>

## Notes

* **Auto-mocking**: `__mocks__` directory support is not yet implemented. [File an issue](https://github.com/oven-sh/bun/issues) if this is blocking your migration.
* **ESM live bindings**: Bun patches JavaScriptCore to allow updating ESM export values at runtime, which enables live binding updates after a module is mocked.
* **Path resolution**: `mock.module()` resolves paths the same way `import` does — relative paths, absolute paths, and package names are all supported.
