Recipe — wire vitest in two modes
The example oracle ships a single config that runs unit tests by default and switches to integration mode with--mode int. Copy it.
File: vitest.config.ts
package.json:
The two layers
All three live under
apps/qiforge-example/test/integration/ — read weather.int.test.ts for a complete example.
Setup file
File:test/integration/setup.ts
.env first (runtime config), then layers .env.integration on top with override: true (test-only credentials). Then it registers LangChain matchers and quiets logs.
Each
.int.test.ts file declares its required env up front and throws on missing values — see the next step. No silent skips.Per-file env gate — fail loud
Every integration test file lists the env it needs and throws at module load if anything is missing.1
Declare required env at the top of the test file
weather.int.test.ts lines 80-94.2
Do not use describe.skipIf for env gates
Silent skips hide broken setups. A throw at file load surfaces immediately when
.env.integration is missing or incomplete.Unit test — createTestRuntime
createTestRuntime returns a TestRuntime directly (no { runtime } wrapper). Drive it through that object’s methods — listTools, invokeTool, invokeMiddleware, invokeSubAgent, listCapabilities, getManifest, assertNoCollisions, assertManifestValid.
createTestRuntime resolves plugins, populates the six registries, and builds a RuntimeContext it hands to tool handlers — but does not boot Nest, talk to Matrix, or call the LLM (every ambient service is a mock). Pass plugin env through config. Use for fast, focused tests: tool registration, manifest validity, middleware hooks, and sub-agent prompts in isolation.
createTestRuntime options take config (plugin env, read as ctx.config) — not env. Its mocks.fetch is exposed for handlers that read it explicitly; it is not auto-installed onto globalThis.fetch. To stub a plugin’s upstream HTTP calls deterministically, use the Tier A createIntegrationRuntime, whose fetch option does replace globalThis.fetch. The full TestRuntime surface lives in create-test-runtime.ts.Tier A integration — direct invoke
createIntegrationRuntime accepts more than plugins + user: config (plugin env, defaults to process.env), features, delegation, capabilities, session, state, identity, fetch (stub upstreams), and ucan.
Tier B integration — full agent loop
mintAuthInvocation (sent as Authorization: Bearer + X-Auth-Type: ucan) is the primary auth artifact the runtime expects; mintUserDelegation (sent as x-ucan-delegation) carries the capabilities plugins use for downstream authorization. ChatClient sends both when supplied — that’s the production path. A delegation-only new ChatClient(url, { delegation }) still authenticates via the migration fallback, which is why older tests pass without an invocation.weather.int.test.ts. Cross-plugin chains: agent-scenarios.int.test.ts. Boot smoke: boot.int.test.ts.
Why the vitest config looks like that
fileParallelism: false
fileParallelism: false
Integration tests share a single Matrix admin user. Two test files booting in parallel collide on Matrix’s one-time key uploads at the homeserver. Run sequentially.
120s timeouts
120s timeouts
Real Nest boot, Matrix sync, and LLM round-trips run 5-30s each. 120s leaves headroom for retries; pushing higher means cutting scope, not raising the cap.
Assert structurally on streamed events
Assert structurally on streamed events
Tier B uses a real model — response wording drifts. Collect
tool_call events from the stream and assert which tools fired with which args, not on text output.createIntegrationOracle swaps in cheaper test models
createIntegrationOracle swaps in cheaper test models
createIntegrationOracle installs a default resolveModel hook that overrides the main and subagent roles with cheaper test models; every other role falls through to the production resolver. Pass your own hooks to override — caller hooks merge on top per key, so a caller-supplied resolveModel wins.What not to do
Where to read next
Write a plugin
The Weather plugin tests live next to its source.
Environment variables
The
.env.integration requirements per plugin.Runtime context
The object handed to your tool handlers in both unit and integration tests.