Bun provides fake timers to control time-based operations in your tests. This lets you test timeout, interval, and time-dependent code without waiting for real time to pass.
Basic usage
Use jest.useFakeTimers() to enable fake timers:
import { test, expect, jest } from "bun:test";
test("setTimeout with fake timers", () => {
jest.useFakeTimers();
const callback = jest.fn();
setTimeout(callback, 1000);
// No time has passed yet
expect(callback).not.toHaveBeenCalled();
// Fast-forward 1 second
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();
});
Timer APIs
jest.useFakeTimers()
Enables fake timers. All timer APIs will be mocked:
setTimeout
setInterval
setImmediate
Date.now()
new Date()
import { jest } from "bun:test";
jest.useFakeTimers();
jest.useRealTimers()
Restores real timers:
import { jest } from "bun:test";
jest.useFakeTimers();
// ... tests with fake timers
jest.useRealTimers();
// ... tests with real timers
jest.advanceTimersByTime(ms)
Advances all timers by the specified milliseconds:
import { test, expect, jest } from "bun:test";
test("advance timers", () => {
jest.useFakeTimers();
const callback = jest.fn();
setTimeout(callback, 1000);
jest.advanceTimersByTime(500);
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(500);
expect(callback).toHaveBeenCalled();
});
jest.runAllTimers()
Runs all pending timers until there are no more:
import { test, expect, jest } from "bun:test";
test("run all timers", () => {
jest.useFakeTimers();
const callback = jest.fn();
setTimeout(callback, 1000);
setTimeout(callback, 2000);
jest.runAllTimers();
expect(callback).toHaveBeenCalledTimes(2);
});
runAllTimers() will cause an infinite loop if you have recurring timers (like setInterval).
jest.runOnlyPendingTimers()
Runs only the timers that are currently pending:
import { test, expect, jest } from "bun:test";
test("run only pending timers", () => {
jest.useFakeTimers();
const callback = jest.fn(() => {
// This creates a new timer
setTimeout(callback, 1000);
});
setTimeout(callback, 1000);
// Runs only the first timer, not the one created in the callback
jest.runOnlyPendingTimers();
expect(callback).toHaveBeenCalledTimes(1);
});
jest.advanceTimersToNextTimer()
Advances timers to the next timer:
import { test, expect, jest } from "bun:test";
test("advance to next timer", () => {
jest.useFakeTimers();
const callback = jest.fn();
setTimeout(callback, 100);
setTimeout(callback, 200);
jest.advanceTimersToNextTimer();
expect(callback).toHaveBeenCalledTimes(1);
jest.advanceTimersToNextTimer();
expect(callback).toHaveBeenCalledTimes(2);
});
jest.clearAllTimers()
Clears all pending timers:
import { test, expect, jest } from "bun:test";
test("clear all timers", () => {
jest.useFakeTimers();
const callback = jest.fn();
setTimeout(callback, 1000);
jest.clearAllTimers();
jest.runAllTimers();
expect(callback).not.toHaveBeenCalled();
});
jest.getTimerCount()
Returns the number of pending timers:
import { test, expect, jest } from "bun:test";
test("get timer count", () => {
jest.useFakeTimers();
setTimeout(() => {}, 1000);
setTimeout(() => {}, 2000);
expect(jest.getTimerCount()).toBe(2);
jest.advanceTimersToNextTimer();
expect(jest.getTimerCount()).toBe(1);
});
Mocking Date
Fake timers also mock Date:
import { test, expect, jest } from "bun:test";
test("mock Date", () => {
jest.useFakeTimers();
jest.setSystemTime(new Date("2024-01-01"));
expect(Date.now()).toBe(new Date("2024-01-01").getTime());
expect(new Date().toISOString()).toBe("2024-01-01T00:00:00.000Z");
jest.advanceTimersByTime(1000);
expect(Date.now()).toBe(new Date("2024-01-01").getTime() + 1000);
});
jest.setSystemTime()
Sets the system time:
import { jest } from "bun:test";
jest.useFakeTimers();
jest.setSystemTime(new Date("2024-01-01"));
You can also pass a timestamp:
jest.setSystemTime(1704067200000);
jest.getRealSystemTime()
Gets the real system time (even with fake timers enabled):
import { test, expect, jest } from "bun:test";
test("get real system time", () => {
jest.useFakeTimers();
jest.setSystemTime(new Date("2024-01-01"));
expect(Date.now()).toBe(new Date("2024-01-01").getTime());
expect(jest.getRealSystemTime()).toBeGreaterThan(new Date("2024-01-01").getTime());
});
Testing intervals
import { test, expect, jest } from "bun:test";
test("setInterval", () => {
jest.useFakeTimers();
const callback = jest.fn();
setInterval(callback, 1000);
jest.advanceTimersByTime(2500);
expect(callback).toHaveBeenCalledTimes(2);
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalledTimes(3);
});
Testing animations
Test requestAnimationFrame:
import { test, expect, jest } from "bun:test";
test("requestAnimationFrame", () => {
jest.useFakeTimers();
const callback = jest.fn();
requestAnimationFrame(callback);
// Advance by one frame (typically 16ms)
jest.advanceTimersByTime(16);
expect(callback).toHaveBeenCalled();
});
Cleanup
Always restore real timers after tests:
import { afterEach, jest } from "bun:test";
afterEach(() => {
jest.useRealTimers();
});
Or use beforeEach and afterEach:
import { beforeEach, afterEach, jest } from "bun:test";
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
Real-world example
Testing a debounce function:
import { test, expect, jest, beforeEach, afterEach } from "bun:test";
function debounce(fn: Function, delay: number) {
let timeoutId: Timer;
return (...args: any[]) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
}
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
test("debounce delays execution", () => {
const callback = jest.fn();
const debounced = debounce(callback, 1000);
debounced("arg1");
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(999);
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(1);
expect(callback).toHaveBeenCalledWith("arg1");
});
test("debounce cancels previous calls", () => {
const callback = jest.fn();
const debounced = debounce(callback, 1000);
debounced("arg1");
jest.advanceTimersByTime(500);
debounced("arg2");
jest.advanceTimersByTime(500);
// First call was cancelled
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(500);
expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith("arg2");
});
Vitest compatibility
Bun also supports Vitest’s timer APIs:
import { vi } from "bun:test";
vi.useFakeTimers();
vi.advanceTimersByTime(1000);
vi.runAllTimers();
vi.useRealTimers();
All jest.* timer methods are available as vi.* as well.