Overview
Voltro's test story — the @voltro/testing package, the test pyramid (unit handlers/tools → workflow runner → e2e), and the voltro test / voltro e2e CLI commands.
The package
Test utilities live in @voltro/testing — a separate, public package. It is not bundled into a scaffolded project, so the moment you write your first test, add it (and vitest) as a devDependency of the app you're testing:
pnpm --filter @my-app/api add -D @voltro/testing vitestEverything in it is in-memory and synchronous-friendly — no docker, no running server, no real model calls. A handler test boots in milliseconds.
| Export | Use |
|---|---|
makeTestContext(options?) |
The request ctx a handler/tool sees at runtime — subject-scoped ctx.store, clock, email, optional ai. → Unit testing |
mockStore(seed) |
Seed rows: makeTestContext({ store: mockStore({ todos: [...] }) }). |
MockClock / MockEmail / MockLLM |
Deterministic clock, captured email, queued LLM responses. |
makeVoltroTestClient(config) |
The frontend harness — render a component against mocked useSubscription / useMutation, from the @voltro/testing/client subpath. → Component testing |
makeWorkflowRunner({ ctx, workflows }) |
Drive a workflow in-process and assert on its steps. → Workflows |
runDialectParity(fixture) |
Run one fixture across a SQL dialect for portability — from the @voltro/testing/dialect subpath. → Dialect parity |
The test pyramid
Voltro's primitives are designed so the cheapest test covers the most surface. Reach for the lowest layer that proves what you need:
- Unit — handlers, tools, mutations, queries. Call the executor directly with a
makeTestContext()ctx. The store is the real mixin-wrapped store, so tenant scoping, soft-delete, and audit auto-fill all behave as in production — without a database. This is 90% of your tests. See Unit testing. - Component — the reactive frontend.
makeVoltroTestClientrenders a component against mockeduseSubscription/useMutation, so loading, empty, stream-error and failing-write branches are all assertable without a running server. See Component testing. - Workflow — durable, multi-step logic.
makeWorkflowRunnerdrives a workflow end-to-end in-process and hands back the same step log the dashboard shows, including real retry counts. See Workflows. - Parity — hand-written SQL that must stay portable.
runDialectParityruns a fixed scenario suite against a dialect you stand up. Mostly for dialect-package authors and apps withunsafe()SQL. See Dialect parity. - End-to-end — the running stack.
voltro e2eboots the api + web siblings, runs your tests against the live processes, and tears them down.
Agents are integration tests; tools are unit tests. Don't reach for an e2e harness to assert logic a makeTestContext test can prove.
The CLI
voltro test # vitest against the current app
voltro e2e # boot api + web siblings, run tests, tear downvoltro test is a thin wrapper over vitest — it runs the app's test files with the framework's config. Everything @voltro/testing exports is plain TypeScript you import inside those files; there's no special test runner.
What NOT to do
- Don't spin up Postgres for a handler test.
makeTestContext's in-memory store enforces the same mixin behaviour — tenant scoping, soft-delete, audit — so a unit test catches the same class of bug far faster. Save a real database for dialect parity and e2e. - Don't call a real model in agent tests. Queue deterministic responses with
mockAi/useMockAi(see Unit testing → agents). A flaky test that hits the network on every run is worse than no test. - Don't assert on wall-clock time. Inject
MockClockandadvance(...)deterministically — neversetTimeout+ real sleeps.