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:
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:
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:
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:
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
mockReturnValue
mockReturnValueOnce
mockResolvedValue
mockRejectedValue
Set a fixed return value for all subsequent calls: const getStatus = mock (() => "online" );
getStatus . mockReturnValue ( "offline" );
expect ( getStatus ()). toBe ( "offline" );
Set a return value for the next call only: 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
Set a resolved Promise value (for async mocks): const fetchProfile = mock ( async () => ({ name: "Alice" }));
fetchProfile . mockResolvedValue ({ name: "Bob" });
const user = await fetchProfile ();
expect ( user . name ). toBe ( "Bob" );
Make the mock reject with an error: const fetchData = mock ( async () => ({}));
fetchData . mockRejectedValue ( new Error ( "Network error" ));
await expect ( fetchData ()). rejects . toThrow ( "Network error" );
Controlling implementations
mockImplementation
Replace the mock’s entire implementation:
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:
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.callsArray of arguments for each invocation mockFn.mock.resultsArray of return values for each invocation mockFn.mock.instancesArray of this contexts for each invocation mockFn.mock.lastCallArguments 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:
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:
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.
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
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:
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:
import { mock } from "bun:test" ;
mock . module ( "./api-client" , () => ({
fetchUser: mock ( async ( id : string ) => ({ id , name: "Test User" })),
}));
[ test ]
preload = [ "./my-preload.ts" ]
Global mock management
mock.restore()
Restore all mock functions to their original implementations at once. Useful in afterEach:
import { afterEach , mock } from "bun:test" ;
afterEach (() => {
mock . restore ();
});
mock.restore() does not reset modules overridden with mock.module().
mock.clearAllMocks()
Reset call history for all mocks without restoring implementations:
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
Mocks with complex internal logic are hard to maintain and can introduce bugs of their own. Return the minimum data your test needs. // Good
const mockApi = {
getUser: mock ( async ( id : string ) => ({ id , name: "Test User" })),
};
Restore mocks after each test
Always clean up to prevent test pollution: import { afterEach , mock } from "bun:test" ;
afterEach (() => {
mock . restore ();
mock . clearAllMocks ();
});
interface UserService {
getUser ( id : string ) : Promise < User >;
}
const mockUserService : UserService = {
getUser: mock ( async ( id : string ) => ({ id , name: "Test User" })),
};
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 );
});
Notes
Auto-mocking : __mocks__ directory support is not yet implemented. File an issue 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.