Skip to main content
bun test is deeply integrated with Bun’s runtime. This integration is part of what makes bun test fast.

Environment Variables

NODE_ENV

bun test sets $NODE_ENV to "test" unless it’s already set in the environment or in .env files. Most test runners do the same.
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35btest.ts
import { test, expect } from "bun:test";

test("NODE_ENV is set to test", () => {
  expect(process.env.NODE_ENV).toBe("test");
});
You can override this by setting NODE_ENV explicitly:
terminal
NODE_ENV=development bun test

TZ (Timezone)

bun test uses UTC (Etc/UTC) as the time zone unless the TZ environment variable overrides it. This keeps date and time behavior consistent across machines.
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35btest.ts
import { test, expect } from "bun:test";

test("timezone is UTC by default", () => {
  const date = new Date();
  expect(date.getTimezoneOffset()).toBe(0);
});
To test with a specific time zone:
terminal
TZ=America/New_York bun test

Test Timeouts

Each test has a default timeout of 5000ms (5 seconds). Tests that exceed it fail.

Global Timeout

Change the timeout globally with the --timeout flag:
terminal
bun test --timeout 10000  # 10 seconds

Per-Test Timeout

Set a per-test timeout as the third argument to the test function:
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35btest.ts
import { test, expect } from "bun:test";

test("fast test", () => {
  expect(1 + 1).toBe(2);
}, 1000); // 1 second timeout

test("slow test", async () => {
  await new Promise(resolve => setTimeout(resolve, 8000));
}, 10000); // 10 second timeout

Infinite Timeout

Use 0 or Infinity to disable the timeout:
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35btest.ts
test("test without timeout", async () => {
  // This test can run indefinitely
  await someVeryLongOperation();
}, 0);

Error Handling

Unhandled Errors

bun test tracks unhandled promise rejections and errors that occur between tests. If any occur, the final exit code is non-zero, even if all tests pass. This helps catch errors in asynchronous code that might otherwise go unnoticed:
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35btest.ts
import { test } from "bun:test";

test("test 1", () => {
  // This test passes
  expect(true).toBe(true);
});

// This error happens outside any test
setTimeout(() => {
  throw new Error("Unhandled error");
}, 0);

test("test 2", () => {
  // This test also passes
  expect(true).toBe(true);
});

// The test run will still fail with a non-zero exit code
// because of the unhandled error

Promise Rejections

Unhandled promise rejections are also caught:
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35btest.ts
import { test } from "bun:test";

test("passing test", () => {
  expect(1).toBe(1);
});

// This will cause the test run to fail
Promise.reject(new Error("Unhandled rejection"));

Custom Error Handling

You can set up custom error handlers in your test setup:
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35btest-setup.ts
process.on("uncaughtException", error => {
  console.error("Uncaught Exception:", error);
  process.exit(1);
});

process.on("unhandledRejection", (reason, promise) => {
  console.error("Unhandled Rejection at:", promise, "reason:", reason);
  process.exit(1);
});

CLI Flags Integration

Several Bun CLI flags also work with bun test:

Memory Usage

terminal
# Reduces memory usage for the test runner VM
bun test --smol

Debugging

terminal
# Attaches the debugger to the test runner process
bun test --inspect
bun test --inspect-brk

Module Loading

terminal
# Runs scripts before test files (useful for global setup/mocks)
bun test --preload ./setup.ts

# Sets compile-time constants
bun test --define "process.env.API_URL='http://localhost:3000'"

# Maps file extensions to built-in loaders
bun test --loader .svg:text

# Uses a different tsconfig
bun test --tsconfig-override ./test-tsconfig.json

# Sets package.json conditions for module resolution
bun test --conditions development

# Loads environment variables for tests
bun test --env-file .env.test
# Affect any network requests or auto-installs during test execution
bun test --prefer-offline
bun test --frozen-lockfile

Watch and Hot Reloading

Watch Mode

With the --watch flag, the test runner watches for file changes and re-runs tests.
terminal
bun test --watch

Hot Reloading

The --hot flag is similar, but more aggressive about preserving state between runs:
terminal
bun test --hot
For most tests, use --watch: it gives better isolation between runs.

Global Variables

The following globals are available in test files without importing:
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35btest.ts
// All of these are available globally
test("global test function", () => {
  expect(true).toBe(true);
});

describe("global describe", () => {
  beforeAll(() => {
    // global beforeAll
  });

  it("global it function", () => {
    // it is an alias for test
  });
});

// Jest compatibility
jest.fn();

// Vitest compatibility
vi.fn();
You can also import them explicitly:
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35btest.ts
import { test, it, describe, expect, beforeAll, beforeEach, afterAll, afterEach, jest, vi } from "bun:test";

Process Integration

Exit Codes

bun test uses standard exit codes:
  • 0: All tests passed, no unhandled errors
  • 1: Test failures or unhandled errors occurred

Signal Handling

The test runner handles common signals:
terminal
# Gracefully stops test execution
kill -SIGTERM <test-process-pid>

# Immediately stops test execution
kill -SIGKILL <test-process-pid>

Environment Detection

Bun automatically detects certain environments and adjusts behavior:
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35btest.ts
// GitHub Actions detection
if (process.env.GITHUB_ACTIONS) {
  // Bun automatically emits GitHub Actions annotations
}

// CI detection
if (process.env.CI) {
  // Certain behaviors may be adjusted for CI environments
}

Performance Considerations

Single Process

The test runner runs all tests in a single process by default. This provides:
  • Faster startup - No need to spawn multiple processes
  • Shared memory - Efficient resource usage
  • Simple debugging - All tests in one process
However, this means:
  • Tests share global state (use lifecycle hooks to clean up)
  • One test crash can affect others
  • No true parallelization of individual tests

Memory Management

terminal
# Monitor memory usage
bun test --smol  # Reduces memory footprint

# For large test suites, consider splitting files
bun test src/unit/
bun test src/integration/

Test Isolation

Since tests run in the same process, ensure proper cleanup:
https://mintcdn.com/bun-1dd33a4e/JUhaF6Mf68z_zHyy/icons/typescript.svg?fit=max&auto=format&n=JUhaF6Mf68z_zHyy&q=85&s=7ac549adaea8d5487d8fbd58cc3ea35btest.ts
import { afterEach } from "bun:test";

afterEach(() => {
  // Clean up global state
  global.myGlobalVar = undefined;
  delete process.env.TEST_VAR;

  // Restore mocked functions if needed
  jest.restoreAllMocks();
});