---
url: https://huldoser.github.io/test-inventory/frameworks/bun.md
description: >-
  Scan Bun tests without running them, interpret the inventory, and check Bun
  syntax and limitations.
---

# Bun

This guide covers **Bun 1.2.23 or newer**. You need Node.js 22.12 or newer to run test-inventory and an
existing JavaScript or TypeScript test suite. The scanner reads your source without executing your tests or starting
a browser. These are minimum supported versions; [CI comparisons](/guide/runners#checked-in-ci) list tested versions.

[Choose another framework](/frameworks/) · [Use an AI client](/guide/agents)

## Install

```sh
npm install --save-dev test-inventory
```

## Scan your suite

Run from the folder containing your project's `package.json`:

```sh
npx test-inventory --framework bun --pretty --output inventory.json
```

To select files explicitly, use quoted globs. This example matches the `quote-gateway` fixture used below;
adjust the globs to your suite's layout:

```sh
npx test-inventory "src/**/*.test.ts" "src/**/*.spec.ts" --framework bun --output inventory.json
```

The command replaces `inventory.json` once the new output is complete. It does not change your test files.

## Check the result

Print the counts and diagnostics from the saved file:

```sh
node -e "const i = require('./inventory.json'); console.log(i.summary); console.log(i.diagnostics)"
```

For matching tests, `summary.fileCount` and `summary.testCount` should be greater than zero. Review diagnostics
even when the command exits with 0: errors can mean tests are missing. If the result is empty or unexpected,
use [troubleshooting](/guide/troubleshooting).

The inventory describes source declarations, not execution results. Conditional skips are not proof that a run
skipped the test. Loops and tables are one record by default; supported cases can be expanded with `--expand-cases`.
Expected-to-fail tests execute and check for failure; they are not disabled tests.

Tests created by unreadable helpers can be missing without a diagnostic. A dependency hash does not prove complete
coverage of runtime dependencies. Read the [shared limits](/guide/limits) before using scans to make CI decisions.

## Find skipped tests

```sh
npx test-inventory --framework bun --state skip --fields fullName,state,condition,reason --pretty
```

This uses the same file selection as the first scan. If you supplied globs, `--root` or `--framework-version`,
repeat those options here.

This selects source declarations marked skip; it is not a report of a test run.
See [query recipes](/guide/recipes) for other inventory queries.

## Example

`quote-gateway` tests a quote gateway with Bun's `bun test`, using Bun's own chains, as `test.skipIf(condition)`, and
a `bunfig.toml` that runs its routing specs at once.

```toml excerpt
[test]
root = "src"
retry = 1
concurrentTestGlob = "**/routing/*.spec.ts"
```

```sh
npx test-inventory "src/**/*.test.ts" "src/**/*.spec.ts" --framework bun --output inventory.json
```

Without the globs and `--framework`, the scan finds the same files from Bun's patterns under `[test] root`, and Bun from
the `test` script. `skipIf` is a condition, and `[test] retry` applies to every test:

```json
{
  "fullName": "spread > streams quotes from the venue",
  "state": "skip",
  "isConditional": true,
  "condition": "!process.env.VENUE_URL",
  "retries": 1
}
```

## Bun parameterized tests

`quote-gateway` tests the same spread calculation with two rows of data:

```ts excerpt
// src/quotes/spread.test.ts
test.each([
  { mic: 'XNAS', bid: 99.95, ask: 100.05 },
  { mic: 'XLON', bid: 49.9, ask: 50.1 },
])('quotes the $mic spread', ({ bid, ask }) => {
  expect(spreadBps({ bid, ask })).toBeGreaterThan(0);
});
```

First scan the declarations:

```sh
npx test-inventory "src/**/*.test.ts" "src/**/*.spec.ts" --framework bun --pretty
```

The table is one record with two cases. This is an excerpt:

```json
{
  "fullName": "spread > quotes the $mic spread",
  "caseCount": 2,
  "caseIndex": null
}
```

To list each resolvable case separately, add `--expand-cases` to the same scan:

```sh
npx test-inventory "src/**/*.test.ts" "src/**/*.spec.ts" --framework bun --expand-cases --pretty
```

The table becomes two records with their substituted titles and zero-based indexes. These are record excerpts:

```json
{
  "fullName": "spread > quotes the XNAS spread",
  "caseCount": 1,
  "caseIndex": 0
}
```

```json
{
  "fullName": "spread > quotes the XLON spread",
  "caseCount": 1,
  "caseIndex": 1
}
```

Expansion follows the detected Bun version's title rules. If your project does not record its Bun version,
pass `--framework-version` with the version you use to both commands.
Unresolved data stays one record with a `cases-not-expanded` warning. See [Bun's table syntax](/frameworks/bun#supported-syntax)
for the supported title formats. The scanner reads the table without running Bun or executing either test case.

## Configuration and limits

The scan reads the root `bunfig.toml` `[test]` table. Without it, files have no config.
Explicit globs count as files given to `bun test`. Supported settings include `root`, `pathIgnorePatterns`,
`concurrentTestGlob` and, from Bun 1.3, `retry`. Invalid pattern settings are reported.

`[test].preload` files enter dependency hashes but are not executed. A focused test can make Bun omit other
tests from its run report; the inventory keeps all declarations and their `isOnly` flags.
Run the scanner with Node.js, even when the scanned tests use Bun.

See [Bun file selection](/reference/output#bun-file-selection) for exact defaults and version differences.

## Supported syntax

Bun 1.2.23 and newer, which runs tests the way 1.3 and 1.4 do; see [framework version](/guide/cli#framework-version).

### Where test comes from

| Form               | Example                                                                                                                                                                               |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Globals            | `test`, `it`, `describe`, `xtest`, `xit`, `xdescribe` and the hooks, which `bun test` sets in its test files                                                                          |
| Imports            | `import { describe, test } from 'bun:test'`, a namespace or default import, or `require('bun:test')`; imports from `@jest/globals` and `vitest` too, which Bun rewrites to `bun:test` |
| Kept in a variable | `const later = test.skip`: only what both branches of a choice share applies, also of one written in place, as `(process.env.CI ? test.skip : test)(...)`                             |
| Local helpers file | `import { it } from './helpers'` or `require('./helpers')`, followed through local imports, and helper functions that declare tests, read at each call                                |

### Calls

| Call                                                                               | Read as                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `test(title, fn, options)`, `test(title, options, fn)`                             | A test. Options are a number, the timeout, or `{ timeout, retry, repeats }`. A `retry` above 0 with any `repeats`, 0 too, is an `invalid-chain` error from Bun 1.3, which refuses them together.                                                                                                                                                                                                                                                                                                                                       |
| `.skip`, `.todo`, `.failing`, `.only`, `.concurrent`, `.serial`                    | A skipped, todo or `expectedToFail` test, `isOnly`, or `isParallel`. A chain may hold them in any order, but only one of `skip`, `todo` and `failing`, one `only`, and one of `concurrent` and `serial`; another is an `invalid-chain` error: Bun fails the file. `skip` wins over `todo`, and `todo` over `failing`.                                                                                                                                                                                                                  |
| `.if(condition)`, `.skipIf`, `.todoIf`, `.failingIf`, `.concurrentIf`, `.serialIf` | A state under a condition: `if` skips when its condition is false, the others set theirs when it is true. A condition that holds counts as its mode in the chain, so `test.todoIf(true).failing` is an `invalid-chain` error as `test.todo.failing` is; one the scan can't work out counts as not holding. A describe can't be `failing`.                                                                                                                                                                                              |
| `.each(table)`                                                                     | One record with `caseCount`, as for Jest; with `expandCases`, each case titled as Bun titles it: `%s` a string, `%d` and `%f` a number, `%i` an integer, `%j` and `%o` JSON, `%#` the index, `%%` a `%`, and `$key` a value of an object case, quoted before Bun 1.3. A value of another type leaves its placeholder as written, and once the values are used up, the rest stays as written. `%p`, and `$key` of an object, keep the cases as one record. A table that isn't an array is an `invalid-chain` error: Bun fails the file. |
| `test(title)` without a function                                                   | A `test-without-body` error: Bun fails the file. `test.skip(title)` and `test.todo(title)` are allowed; `test.todo` with a function is todo too.                                                                                                                                                                                                                                                                                                                                                                                       |
| `beforeAll`, `afterAll`, `beforeEach`, `afterEach`                                 | Hooks.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |

A refusal inside a describe fails that describe, whose tests are `notLoaded` with `stateSource: "suite"`; tests outside it still run.

A `.only` makes Bun run only the focused tests of its file, and from 1.3 with `--only`, none of the files without one;
from 1.3, with `CI=true`, as in GitHub Actions, it fails its file instead. The other tests of the file are listed as
written, with `isOnly` on the focused ones.

The [shared syntax rules](/reference/syntax#in-every-runner) also apply.

## Next

* [Shared limits](/guide/limits): imports, generated tests, conditions and dependency coverage.
* [Key concepts](/guide/concepts): counts, states, IDs and hashes.
* [Output reference](/reference/output) and [diagnostics](/reference/diagnostics): interpret every field and warning.
* [CLI](/guide/cli), [library](/guide/library) and [MCP](/guide/agents): use the same inventory through each interface.
* [Compare with Bun](/guide/runners#bun-s-list): differences from runner collection and execution reports.
