Testing Package
Shared Runlane test primitives and fixtures.
@runlane/testing contains reusable test support and executable adapter conformance suites for Runlane packages. It is a development dependency for package and adapter tests, not a second runtime, storage adapter, transport, or lane implementation.
Install it in packages that own Runlane tests:
pnpm add -D @runlane/testingInstall vitest only when the package imports the Vitest binding from @runlane/testing/vitest:
pnpm add -D vitestThe root @runlane/testing entry point is runner-agnostic and does not import Vitest.
Use it for deterministic fixtures and small observation helpers:
import { asId, RunStatus } from '@runlane/contracts'
import { appendQueuedRun, createControlledClock, waitForRunStatus } from '@runlane/testing'
const clock = createControlledClock()
const queuedRunFixture = await appendQueuedRun(lane, clock, asId<'run'>('run_123'))
await waitForRunStatus(runtime, queuedRunFixture.queuedRun.id, RunStatus.Succeeded)appendQueuedRun() only needs a lane-shaped value with storage. waitForRunStatus() only needs { environment, lane: { storage } }, which lets tests observe durable state without depending on a full runtime type.
Entry Points
| Entry point | Use |
|---|---|
@runlane/testing | Runner-agnostic conformance definitions plus clocks, fixtures, observation helpers, fake loggers, storage stubs, and acknowledging transports. |
@runlane/testing/vitest | Vitest registration helpers that bind the same conformance suites to Vitest's describe() and test(). |
Fixtures And Helpers
| Helper family | Exports |
|---|---|
| Clocks | createControlledClock(), createStepClock() |
| Polling and observation | waitForCondition(), waitForRunStatus(), waitForRunEvent(), listRunEventTypes() |
| Assertions | getAt() |
| Run fixtures | createQueuedRunData(), createScheduledRunData(), createSucceededRunData(), appendQueuedRun(), appendScheduledRun(), appendSucceededRun(), claimQueuedRun(), getLease() |
| Shared identities | testEnvironment, testQueue, testSystemActor, testTaskId, testWorkerId |
| Delivery and transport fixtures | createDeliveryMessage(), createAcknowledgingTransport() |
| Logger and storage stubs | createFakeLogger(), createStorageAdapterFixture() |
| Runtime harness | createRuntimeHarness() |
createStorageAdapterFixture() is for focused unit tests. Its methods reject by default, and the test supplies only the storage methods it expects to call. Do not use it as evidence that a real adapter conforms to the storage contract; use storage conformance for that.
Conformance Suites
Conformance is the executable form of the primitive contracts. Adapter and lane packages provide fresh resources to the suite; each suite verifies one boundary instead of pretending a composed lane owns runtime behavior.
The stack is:
- storage conformance proves storage adapter shape, event atomicity, defensive copies, stale sequence conflicts, idempotency ownership, lease and bounded-queue races, outbox transitions, run scans, operator reads, schedule occurrence claims, and pruning behavior.
- transport conformance proves transport adapter shape, publish command validation, rejected promises for malformed publish commands, and one valid publish outcome per wakeup attempt.
- lane composition conformance proves
Laneshape, capability exposure, and the composed storage-to-transport outbox publish flow.
Core runtime behavior is tested in @runlane/core against conforming adapters. Lane packages can add product-level integration tests over createRunlane() when they promise a ready-to-use experience, as @runlane/lane-local does. Worker loops, schedules, operator APIs, retry, release, and cancellation do not belong in lane composition conformance or lane implementation code.
Vitest users can use the first-party runner adapter:
import { createLocalStorage } from '@runlane/local-adapters'
import { describeStorageConformance } from '@runlane/testing/vitest'
describeStorageConformance({
createStorage: createLocalStorage,
name: 'local memory',
})Jest or another runner can bind the same suite without pulling in Vitest:
import { createLocalStorage } from '@runlane/local-adapters'
import { defineStorageConformanceSuite, runConformanceSuite } from '@runlane/testing'
runConformanceSuite(
{
describe,
test,
},
defineStorageConformanceSuite({
createStorage: createLocalStorage,
name: 'local memory',
}),
)Use conformance for the shared contract. Keep adapter-specific tests beside the adapter for migrations, provider errors, SQLSTATE or SDK mapping, connection lifecycle, backend-specific performance or locking behavior, and local implementation regression history.
How Registration Works
The Vitest helpers only register tests. They do not discover local, Postgres, SQS, or custom implementations. The adapter package chooses the implementation by passing a factory:
import { createLocalTransport } from '@runlane/local-adapters'
import { describeTransportConformance } from '@runlane/testing/vitest'
describeTransportConformance({
createTransport: createLocalTransport,
name: 'local memory',
})The same pattern exists for lane composition:
import { createLocalLane } from '@runlane/lane-local'
import { describeLaneCompositionConformance } from '@runlane/testing/vitest'
describeLaneCompositionConformance({
createLane: createLocalLane,
name: 'local',
})Each conformance test calls the supplied factory for its own fresh resource. If the returned adapter or lane has start() or close() hooks, conformance runs them around that single test. If the factory needs external cleanup, return an envelope with the resource key and optional cleanup:
- storage:
{ storage, cleanup? } - transport:
{ transport, cleanup? } - lane:
{ lane, cleanup? }
cleanup() runs after close(). The Postgres and Postgres/SQS package tests use this shape to create one migrated schema per conformance resource and drop it after the test.
Internally, helpers such as describeStorageConformance(...) and describeLaneCompositionConformance(...) call their matching define*ConformanceSuite(options) functions.
The suite definition is a runner-agnostic tree of suite names and test bodies. runConformanceSuite(runner, suite) walks that tree and calls the runner's describe(...) and test(...).
The first-party @runlane/testing/vitest export supplies Vitest as that runner; Jest users can pass Jest's globals directly to runConformanceSuite().
The important separation is:
define*ConformanceSuite(...): runner-agnostic suite definition.runConformanceSuite(...): bridge from a suite tree to any compatible test runner.@runlane/testing/vitest: first-party Vitest registration helpers.*.conformance.test.ts: adapter package-owned test files that choose which implementation factory is under test.
What Conformance Does Not Prove
Conformance is necessary for adapter authors, but it is not an operational test plan. It does not prove:
- migrations or schema upgrade paths
- provider credentials, IAM, queue provisioning, redrive policies, FIFO grouping, or regional behavior
- driver-specific error mapping beyond the contract errors the suite exercises
- production performance, locking behavior, or query plans
- runtime behavior such as task registration, workers, schedules, retry, release, cancellation, pruning orchestration, or operator APIs
Those tests stay in the package that owns the adapter, lane, or runtime behavior. For example, @runlane/postgres-storage adds migration and query-plan tests beside storage conformance, and @runlane/lane-local adds createRunlane() integration tests beside lane composition conformance.
Boundary Rules
The testing package depends on contracts, utilities, and test-only libraries. It does not depend on @runlane/core, @runlane/lane-local, or adapter packages. The root @runlane/testing export is runner-agnostic; @runlane/testing/vitest is the first-party Vitest binding. Package-specific tests pass their own adapters, transports, or lanes into conformance factories.
The runtime harness is intentionally thin. It supplies shared clock and environment defaults to a caller-provided factory; concrete runtime packages still own their construction rules.
Inside this repository, workspace tests resolve Runlane packages to source through the shared Vitest config. That keeps test execution on the code under edit instead of stale package dist output.