328 lines
7.6 KiB
Plaintext
328 lines
7.6 KiB
Plaintext
---
|
|||
|
|
title: "Runtime behavior"
|
||
|
|
description: "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.
|
||
|
|
|
||
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
||
|
|
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:
|
||
|
|
|
||
|
|
```bash terminal icon="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.
|
||
|
|
|
||
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
||
|
|
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:
|
||
|
|
|
||
|
|
```bash terminal icon="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:
|
||
|
|
|
||
|
|
```bash terminal icon="terminal"
|
||
|
|
bun test --timeout 10000 # 10 seconds
|
||
|
|
```
|
||
|
|
|
||
|
|
### Per-Test Timeout
|
||
|
|
|
||
|
|
Set a per-test timeout as the third argument to the test function:
|
||
|
|
|
||
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
||
|
|
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:
|
||
|
|
|
||
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
||
|
|
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, `bun test` exits with a non-zero code even when no test failed. In both examples below the error happens while the file is being loaded, so the file's tests are not run at all.
|
||
|
|
|
||
|
|
This helps catch errors in asynchronous code that might otherwise go unnoticed:
|
||
|
|
|
||
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
||
|
|
import { test, expect } from "bun:test";
|
||
|
|
|
||
|
|
test("test 1", () => {
|
||
|
|
expect(true).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
// This error happens outside any test
|
||
|
|
queueMicrotask(() => {
|
||
|
|
throw new Error("Unhandled error");
|
||
|
|
});
|
||
|
|
|
||
|
|
test("test 2", () => {
|
||
|
|
expect(true).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
// bun test reports this as "Unhandled error between tests", does not run
|
||
|
|
// this file's tests (0 pass, 1 error), and exits with code 1
|
||
|
|
```
|
||
|
|
|
||
|
|
### Promise Rejections
|
||
|
|
|
||
|
|
The test runner also catches unhandled promise rejections:
|
||
|
|
|
||
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
||
|
|
import { test, expect } from "bun:test";
|
||
|
|
|
||
|
|
test("test 1", () => {
|
||
|
|
expect(1).toBe(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
// bun test reports this as "Unhandled error between tests", does not run
|
||
|
|
// this file's tests, and exits with code 1
|
||
|
|
Promise.reject(new Error("Unhandled rejection"));
|
||
|
|
```
|
||
|
|
|
||
|
|
### Custom Error Handling
|
||
|
|
|
||
|
|
You can set up custom error handlers in your test setup:
|
||
|
|
|
||
|
|
```ts title="test-setup.ts" icon="/icons/typescript.svg"
|
||
|
|
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
|
||
|
|
|
||
|
|
```bash terminal icon="terminal"
|
||
|
|
# Reduces memory usage for the test runner VM
|
||
|
|
bun test --smol
|
||
|
|
```
|
||
|
|
|
||
|
|
### Debugging
|
||
|
|
|
||
|
|
```bash terminal icon="terminal"
|
||
|
|
# Attaches the debugger to the test runner process
|
||
|
|
bun test --inspect
|
||
|
|
bun test --inspect-brk
|
||
|
|
```
|
||
|
|
|
||
|
|
### Module Loading
|
||
|
|
|
||
|
|
```bash terminal icon="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
|
||
|
|
```
|
||
|
|
|
||
|
|
### Installation-related Flags
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 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.
|
||
|
|
|
||
|
|
```bash terminal icon="terminal"
|
||
|
|
bun test --watch
|
||
|
|
```
|
||
|
|
|
||
|
|
### Hot Reloading
|
||
|
|
|
||
|
|
The `--hot` flag is similar, but more aggressive about preserving state between runs:
|
||
|
|
|
||
|
|
```bash terminal icon="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:
|
||
|
|
|
||
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
||
|
|
// 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:
|
||
|
|
|
||
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
||
|
|
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:
|
||
|
|
|
||
|
|
```bash terminal icon="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:
|
||
|
|
|
||
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
||
|
|
// 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
|
||
|
|
- **Simpler 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
|
||
|
|
|
||
|
|
```bash terminal icon="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:
|
||
|
|
|
||
|
|
```ts title="test.ts" icon="/icons/typescript.svg"
|
||
|
|
import { afterEach, jest } from "bun:test";
|
||
|
|
|
||
|
|
afterEach(() => {
|
||
|
|
// Clean up global state
|
||
|
|
global.myGlobalVar = undefined;
|
||
|
|
delete process.env.TEST_VAR;
|
||
|
|
|
||
|
|
// Restore mocked functions if needed
|
||
|
|
jest.restoreAllMocks();
|
||
|
|
});
|
||
|
|
```
|