skills/bun-test-runtime-behavior/SKILL.md
Learn about Bun test's runtime integration, environment variables, timeouts, and error handling
npx skillsauth add jarle/bun-skills Bun Runtime behaviorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Learn about Bun test's runtime integration, environment variables, timeouts, and error handling
bun test is deeply integrated with Bun's runtime. This is part of what makes bun test fast and simple to use.
bun test automatically sets $NODE_ENV to "test" unless it's already set in the environment or via .env files. This is standard behavior for most test runners and helps ensure consistent test behavior.
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 test
By default, all bun test runs use UTC (Etc/UTC) as the time zone unless overridden by the TZ environment variable. This ensures consistent date and time behavior across different development environments.
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 timezone:
TZ=America/New_York bun test
Each test has a default timeout of 5000ms (5 seconds) if not explicitly overridden. Tests that exceed this timeout will fail.
Change the timeout globally with the --timeout flag:
bun test --timeout 10000 # 10 seconds
Set timeout per test as the third parameter 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 timeout
Use 0 or Infinity to disable timeout:
test("test without timeout", async () => {
// This test can run indefinitely
await someVeryLongOperation();
}, 0);
bun test tracks unhandled promise rejections and errors that occur between tests. If such errors occur, the final exit code will be non-zero (specifically, the count of such errors), 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 error
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"));
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);
});
Several Bun CLI flags can be used with bun test to modify its behavior:
# Reduces memory usage for the test runner VM
bun test --smol
# Attaches the debugger to the test runner process
bun test --inspect
bun test --inspect-brk
# 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'"
# Configures custom loaders
bun test --loader .special:special-loader
# 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
When running bun test with the --watch flag, the test runner will watch for file changes and re-run affected tests.
bun test --watch
The test runner is smart about which tests to re-run:
import { add } from "./math.js";
import { test, expect } from "bun:test";
test("addition", () => {
expect(add(2, 3)).toBe(5);
});
If you modify math.js, only math.test.ts will re-run, not all tests.
The --hot flag provides similar functionality but is more aggressive about trying to preserve state between runs:
bun test --hot
For most test scenarios, --watch is the recommended option as it provides better isolation between test runs.
The following globals are automatically available in test files without importing (though they can be imported from bun:test if preferred):
// 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 if you prefer:
import { test, it, describe, expect, beforeAll, beforeEach, afterAll, afterEach, jest, vi } from "bun:test";
bun test uses standard exit codes:
0: All tests passed, no unhandled errors1: Test failures occurred>1: Number of unhandled errors (even if tests passed)The test runner properly handles common signals:
# Gracefully stops test execution
kill -SIGTERM <test-process-pid>
# Immediately stops test execution
kill -SIGKILL <test-process-pid>
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
}
The test runner runs all tests in a single process by default. This provides:
However, this means:
# Monitor memory usage
bun test --smol # Reduces memory footprint
# For large test suites, consider splitting files
bun test src/unit/
bun test src/integration/
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;
// Reset modules if needed
jest.resetModules();
});
development
Using TypeScript with Bun, including type definitions and compiler options
development
Learn how to write tests using Bun's Jest-compatible API with support for async tests, timeouts, and various test modifiers
testing
Learn how to use snapshot testing in Bun to save and compare output between test runs
testing
Test Reporters