Runtime behavior
Learn about Bun test's runtime integration, environment variables, timeouts, and error handling
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.
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:
$ NODE_ENV=development bun testTZ (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.
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:
$ TZ=America/New_York bun testTest 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:
$ bun test --timeout 10000 # 10 secondsPer-Test Timeout
Set a per-test timeout as the third argument to the test function:
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 timeoutInfinite Timeout
Use 0 or Infinity to disable the timeout:
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:
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 errorPromise Rejections
Unhandled promise rejections are also caught:
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:
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
# Reduces memory usage for the test runner VM
$ bun test --smolDebugging
# Attaches the debugger to the test runner process
$ bun test --inspect
$ bun test --inspect-brkModule Loading
# 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.testInstallation-related Flags
# Affect any network requests or auto-installs during test execution
$ bun test --prefer-offline
$ bun test --frozen-lockfileWatch and Hot Reloading
Watch Mode
With the --watch flag, the test runner watches for file changes and re-runs tests.
$ bun test --watchHot Reloading
The --hot flag is similar, but more aggressive about preserving state between runs:
$ bun test --hotFor most tests, use --watch: it gives better isolation between runs.
Global Variables
The following globals are available in test files without importing:
// 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:
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 errors1: Test failures or unhandled errors occurred
Signal Handling
The test runner handles common signals:
# 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:
// 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
# 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:
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();
});