Skip to content

Property, scenario, and snapshot API

StableSince v0.1.0Requires fast-check

Use this when you need the option tables and type contracts behind the generative and baseline-based capabilities: property tests, BDD scenarios, snapshots, and the helper subpaths that type them. Each capability has its own guide for the workflow — property-based testing, BDD scenarios, and snapshot testing.

BDD scenarios are experimental and require the @aboviq/bun-test-cucumber peer; the property and snapshot APIs are stable. test.prop and test.scenario.prop require the fast-check peer (>=4.0.0).

test.prop(title, arbitraryFactory, fn, options?)

Runs a property test. The arbitrary factory receives the fast-check API; the test body receives the fixtures first and the generated values second.

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

test.prop(
"encoding is reversible",
(fc) => ({ text: fc.string(), key: fc.integer({ min: 1, max: 255 }) }),
async ({ tmpdir }, { text, key }) => {
tmpdir.write("value.txt", text);
expect(decode(encode(tmpdir.read("value.txt"), key), key)).toBe(text);
},
{ numRuns: 100 },
);
Argument / optionTypeBehaviour
titlestringThe bun test case name.
arbitraryFactory(fc) => Record<string, Arbitrary>Receives the fast-check API; returns one arbitrary per generated value.
fn(fixtures, values) => void | Promise<void>The property. Fixtures are auto-detected from the destructured first parameter.
fixturesstring[]Explicit fixture list, for fixtures needed only for their side effect.
timeoutnumberPer-test timeout in milliseconds.
Any other optionfast-check’sPassed straight through to fc.assert, e.g. numRuns, seed, endOnFailure.

Lifecycle. Each generated sample and each shrink step runs through the iteration protocol: session- and file-scoped fixtures are shared for the whole test, test-scoped fixtures are rebuilt per sample and per shrink step and torn down in LIFO order. See the per-sample lifecycle.

test.scenario(title) and test.scenario.prop(title, arbitraryFactory)

Builds one fixture-aware test from fluent given, when, and then steps. Objects returned from given and when are merged into the next step’s context, alongside fixture values. Steps are typed in phase order.

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

test
.scenario("creates a user")
.given("a name", () => ({ name: "Ada" }))
.when("the user is created", async ({ db, name }) => ({
user: await db.users.create({ name }),
}))
.then("the id is assigned", ({ user, expect }) => {
expect(user.id).toBeDefined();
});
MemberBehaviour
test.scenario(title)Starts a given-phase chain.
.given(description, step)Adds a step that produces context. May be repeated.
.when(description, step)Adds a step that performs the action. May be repeated.
.then(description, step)Adds an assertion. Its return value is not merged.
test.scenario.prop(title, arbitraryFactory)Generates the scenario’s initial context with fast-check.
expect in a step’s contextInjected per step: ({ expect, ... }).

Fixture names are detected per step, so a scenario does not repeat a fixture list. test.scenario.prop rebuilds test-scoped fixtures for every generated example and every shrink step.

defineArbitraries and the /pbt subpath

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";
export const userArbitraries = defineArbitraries((fc) => ({
name: fc.string(),
age: fc.nat(),
}));
ExportBehaviour
defineArbitraries(factory)Returns factory unchanged. Types the fc parameter contextually and infers the generated values at each call site.
FastCheckApiThe fast-check API type, for helpers that take an fc parameter.
ArbitraryInputAn arbitrary, or a function returning one.
GeneratedValuesThe values a factory produces, keyed by name.

/bdd step helpers

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

import { givenStep, thenStep, whenStep } from "@archont561/bun-test-utils/bdd";
import type {
GivenChain,
GivenStep,
ScenarioContext,
ThenStep,
WhenStep,
} from "@archont561/bun-test-utils/bdd";
ExportBehaviour
givenStep<In, Out>(fn)Types a given step’s input context and the state it adds. Returns the function unchanged.
whenStep<In, Out>(fn)The same for when.
thenStep<In>(fn)The same for then; the step asserts and returns nothing.
GivenChainThe chain type a reusable sequence function accepts, e.g. (chain: GivenChain) => chain.given(...).
ScenarioContextThe context a step receives: fixture values, scenario state, and expect.

snapshot: value and file snapshots

The fixture is callable and also an object.

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

test("captures states", async ({ snapshot }) => {
snapshot.match(initialState());
snapshot.match(afterClick(), "after-click");
snapshot.matchFile(reportPath, "report");
const profile = await snapshot(async () => fetchProfile("ada"), "ada-profile");
expect(profile.id).toBe("ada");
});
MemberBehaviour
snapshot(callback, name)Runs and awaits callback once, matches its result under the required non-empty name, and returns the original result. Never caches or keys on callback source; a callback error propagates before any write.
match(value, name?)Compares a value against the stored snapshot, or records it on first run. Unnamed calls are auto-numbered.
matchFile(path, name?)Compares the contents of a file.
addSerializer(serializer)Registers a serializer for this test. Return undefined for values it does not handle.
mode, setMode(mode)"match", "update", or "ci". ci is selected when CI is set.
pathThe path of this test’s snapshot file (read-only).

Serialization order. Fixture-local serializers run newest-first, then global serializers newest-first, then the built-ins. Serializers apply recursively to object properties and array entries. Details and failure modes are in Snapshot testing.

/snap: global serializers

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

import {
createSnapshotSerializer,
registerSnapshotSerializer,
resetSnapshotSerializers,
unregisterSnapshotSerializer,
} from "@archont561/bun-test-utils/snap";
import type { Serializer } from "@archont561/bun-test-utils/snap";
ExportBehaviour
createSnapshotSerializer(fn)Registers fn as a process-wide serializer and returns it.
registerSnapshotSerializer(serializer)Registers a Serializer and returns it.
unregisterSnapshotSerializer(serializer)Removes every registration of that exact function; true when it removed one or more.
resetSnapshotSerializers()Clears the global registry. Use at a controlled boundary such as a watch-mode reload.
Serializer(value) => string | undefined.

Errors

CodeRaised when
MISSING_OPTIONAL_DEPENDENCYfast-check or @aboviq/bun-test-cucumber is not installed. The message names the install command.
SNAPSHOT_CIRCULAR_REFERENCEA snapshot value contains a cycle. Details name the snapshot and the value path.
SNAPSHOT_SERIALIZER_FAILEDA snapshot serializer threw. The original error is the cause.
INVALID_API_USAGEA capability was called with arguments or in a state it does not support, such as a callable snapshot without a name.

fast-check failures report the generated counterexample and the seed in the test output; replay them with { seed: ... }.

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