Skip to content

Core runner and fixture API

StableSince v0.1.0

Use this when you need the exact signature of the runner, the options a test accepts, or the fields a fixture definition may set. It is the reference for everything the test object itself does; per-capability APIs live in Built-in fixture API, Property, scenario, and snapshot API, and HTTP, cassette, and browser API.

This page replaces the old single-page API reference: /reference/api/ redirects here, and the section map says where each old section went.

Imports

The root package exports the runner values describe, expect, and test:

Partial — one step of a longer file or workflow — surrounding lines are omitted

import { describe, expect, test } from "@archont561/bun-test-utils";

Four helper-only subpaths export typed definitions and serializer contracts. They never export a runner or a fixture:

Partial — one step of a longer file or workflow — surrounding lines are omitted

import { defineArbitraries } from "@archont561/bun-test-utils/pbt";
import type { ArbitraryInput, FastCheckApi, GeneratedValues } from "@archont561/bun-test-utils/pbt";
import { givenStep, whenStep, thenStep } from "@archont561/bun-test-utils/bdd";
import type { GivenChain, GivenStep, ScenarioContext, ThenStep, WhenStep } from "@archont561/bun-test-utils/bdd";
import {
createSnapshotSerializer,
registerSnapshotSerializer,
resetSnapshotSerializers,
unregisterSnapshotSerializer,
} from "@archont561/bun-test-utils/snap";
import type { Serializer } from "@archont561/bun-test-utils/snap";
import {
defineCallbackSerializer,
registerCallbackSerializer,
unregisterCallbackSerializer,
} from "@archont561/bun-test-utils/vcr";
import type { CallbackSerializer } from "@archont561/bun-test-utils/vcr";

Do not import @archont561/bun-test-utils/std, /dom, /browser, /server, or /snapshot, or any other internal workspace path. Fixtures and execution APIs live on the root test object.

Stability and platforms

The fixture engine and the standard, DOM, snapshot, property-testing, and minimal cassette capabilities are stable and follow semantic versioning. Browser testing and BDD scenarios are experimental and may change in minor versions. Linux and macOS are supported; Windows is not supported in 0.1.x.

The full policy — Bun range, peers, compatibility surfaces, and support channels — is in Stability, compatibility, and support.

test(name, fn, options?)

Defines a fixture-aware test. The fixtures it requests are read from the first parameter’s destructuring pattern; each must exist in the imported test.extend() chain or be one of the built-in root fixtures.

Partial — one step of a longer file or workflow — surrounding lines are omitted

test("uses fixtures", async ({ tmpdir, env }) => {
env.set("APP_MODE", "test");
tmpdir.write("mode.txt", env.get("APP_MODE")!);
});
OptionTypeDefaultBehaviour
fixturesstring[]auto-detectedExplicit fixture list. Replaces detection from the destructured parameter.
timeoutnumberBun’s defaultPer-test timeout in milliseconds, forwarded to bun:test.
iteratebooleanfalseDefers test-scoped fixtures and provides ctx.iterate(fn). See below.

The option object is the third argument: test(name, fn, options).

The iteration protocol (iterate)

With iterate: true, the outer context builds only session- and file-scoped fixtures and provides ctx.iterate(fn). Each await ctx.iterate(fn) call builds a fresh set of test-scoped fixtures for that one call and tears them down in LIFO order afterwards, even when fn throws.

Partial — one step of a longer file or workflow — surrounding lines are omitted

test(
"each iteration gets its own test-scoped state",
async ({ iterate }) => {
const dirs: string[] = [];
await iterate(async (ctx) => {
ctx.tmpdir!.write("attempt.txt", "one");
dirs.push(ctx.tmpdir!.dir);
});
await iterate(async (ctx) => {
dirs.push(ctx.tmpdir!.dir);
});
expect(new Set(dirs).size).toBe(2); // a different directory per call
},
{ fixtures: ["tmpdir"], iterate: true },
);

Two things about that example are easy to get wrong:

  • The test-scoped value arrives in the iterate callback’s context, not in the outer test parameter. In the outer context only session- and file-scoped fixtures are built, so a test-scoped name is undefined there.
  • Declare the names explicitly. The callback’s destructured parameter cannot be introspected, so pass them through options.fixtures (as above) or name them in the test’s own destructuring pattern.

test.prop and test.scenario.prop use this protocol, so each generated sample and each shrink step runs with its own test-scoped state. See the per-sample lifecycle.

test.extend(fixtures)

Returns a new runner that includes your fixtures. The parent runner is not modified, and a test sees exactly the chain it imports.

Partial — one step of a longer file or workflow — surrounding lines are omitted

import { test as base } from "@archont561/bun-test-utils";
export const test = base.extend({
db: {
scope: "file",
setup: async (use) => {
const db = await createDatabase();
await use(db);
await db.close();
},
},
});

Each fixture definition takes:

FieldTypeDefaultBehaviour
setup(use, context) => void | Promise<void>requiredBuilds the value and passes it to use(value). Code after await use(value) is the teardown.
scope"session" | "file" | "test""test"How long the value lives. See Scopes and teardown.
depsstring[]inferredExplicit dependency list. Normally omitted: dependencies come from the destructured second parameter of setup.

The second setup parameter is a FixtureContext: resolved fixture values by name, plus testFile, testName, and scope metadata for the fixture being built.

All fixtures share one flat namespace, built-ins included. Composition is last-definition-wins: a fixture with the same name as a built-in replaces it, and a later extend() replaces an earlier definition. Dependencies resolve by name, so they receive the replacement too. See Composition and overrides.

Runner extensions

Two capabilities add methods to the runner. They are declared like any other test and use the same fixtures.

MethodProvided byReference
test.prop(title, arbitraryFactory, fn, options?)Property-based testing (fast-check peer)Property tests
test.scenario(title)BDD scenarios (@aboviq/bun-test-cucumber peer, experimental)Scenario API
test.scenario.prop(title, arbitraryFactory)Both peersSame section

Errors raised here

CodeRaised whenGuide
UNKNOWN_FIXTUREA test requests a fixture the imported chain does not provideComposition
SCOPE_MISMATCHA fixture depends on a shorter-lived fixture; raised when the test is declaredScope rules
CIRCULAR_DEPENDENCYFixtures depend on each other in a cycleDebugging fixture failures
FIXTURE_USE_NOT_CALLEDA fixture’s setup finished without calling use(value)Same page
FIXTURE_USE_CALLED_TWICEA fixture’s setup called use(value) more than onceSame page

Every error also carries details; prefer it over parsing the message. The human-readable text of the four contractual templates is in the errors reference.

Docs for bun-test-utils v0.1.0 Release notes Report a docs issue