Before: implicit globals & copy-paste chain code
- One-off local chain / fork wiring in every test file.
- Ad hoc wallet funding and client factories.
- Flaky suites that depend on hidden global setup.
After: named fixtures & typed context
Compose
withChain,withFork,withFundedWallet, and more as middleware-style steps.Accumulate typed
publicClient,walletClient,testClient, wallets, and deployments on the scenario context.Keep fork tests honest:
rpcUrl+ a requiredblockNumberfor reproducibility.
Compose scenarios, not config files
Each step receives context, extends it, and calls next(updatedCtx) once—clear
ordering, predictable lifecycle, and tests that read like the product behavior you care about.
Four ways to set chain state—pick the right speed
withChain
Spin up a fresh local Anvil-style runtime when you need isolation and a blank slate.
withFork
Fork mainnet (or any JSON-RPC) with a pinned block height for deterministic integration tests.
withContracts
Inject runtime bytecode at known addresses—fast setup without running constructors.
withDeployments
Deploy with real creation bytecode and constructor semantics when you need the full path.
Proof in a handful of lines
The same pattern you will find in packages/examples/examples/scenarios.test.ts and the
Quickstart: fork, fund, assert.
import { test, expect } from "vitest";
import { parseEther } from "viem";
import { scenario, withFork, withFundedWallet } from "@statecraft/vitest";
test(
"funded wallet on mainnet fork",
scenario(
withFork({
rpcUrl: process.env.MAINNET_RPC_URL!,
blockNumber: 22_000_000n,
}),
withFundedWallet({ balance: parseEther("1") }),
async ({ wallet, publicClient }) => {
const balance = await publicClient!.getBalance({ address: wallet! });
expect(balance).toBe(parseEther("1"));
},
),
);Built for reliability, not magic
Pinned forks — tests target a specific
blockNumber, not “whatever mainnet looks like today.”Explicit runtimes — chain and fork fixtures start runtimes and clean up in
finallyso failures do not leak processes.Optional isolation — snapshot/revert style helpers when you want tighter boundaries between assertions.
Ship scenarios your team can reuse
Install from the monorepo workspace, run bun test, and promote the same fixtures from
examples into your product suite—bounded packages, clear dependency direction, Vitest-native ergonomics.