@tevm/test-node
v1.0.0-next.150
Published
Vite test utils for snapshotting JSON-RPC requests using a Tevm server.
Maintainers
Readme
@tevm/test-node
A utility package for testing applications with Tevm. It provides a simple way to spin up a local, forked Tevm instance with built-in JSON-RPC snapshotting for fast, reliable, and deterministic tests.
Features
- Auto-managed Test Server: Zero-config test server that automatically starts/stops per test file
- JSON-RPC Snapshotting: Automatically caches JSON-RPC requests to disk. Subsequent test runs are served from the cache, making them orders of magnitude faster and immune to network flakiness
- Forking Support: Test against a fork of any EVM-compatible network
- Seamless Vitest Integration: Designed to work perfectly with Vitest's lifecycle hooks
- Automatic Snapshot Placement: Snapshots stored in
__rpc_snapshots__/<testFileName>.snap.jsonnext to test files
Installation
pnpm add -D @tevm/test-node vitest
npm install -D @tevm/test-node vitestQuick Start
import { createTestSnapshotClient } from '@tevm/test-node'
import { http } from 'viem'
const client = createTestSnapshotClient({
fork: {
transport: http('https://mainnet.optimism.io')
}
})
// Use in tests
await client.getBlock({ blockNumber: 123456n })
// Snapshots automatically saved to __rpc_snapshots__/yourTest.spec.ts.snap.jsonWith Viem Client
import { createTestSnapshotTransport } from '@tevm/test-node'
import { createTevmTransport } from '@tevm/memory-client'
import { createClient, http } from 'viem'
import { mainnet } from 'viem/chains'
const cachedTransport = createTestSnapshotTransport({
transport: http('https://mainnet.optimism.io')
})
const client = createClient({
chain: mainnet,
transport: createTevmTransport({
fork: { transport: cachedTransport },
})
})
await client.getBlock({ blockNumber: 123456n })
// Snapshots cached automaticallyAs Standalone Server
import { createTestSnapshotClient } from '@tevm/test-node'
import { createClient, http } from 'viem'
const client = createTestSnapshotClient({
fork: { transport: http('https://mainnet.optimism.io') }
})
// Start HTTP server
await client.server.start()
console.log(client.server.rpcUrl) // http://localhost:8545
// Connect other clients to the server (these will hit the server directly and not be cached)
const otherClient = createClient({
transport: http(client.server.rpcUrl)
})
await client.server.stop()API Reference
createTestSnapshotClient(options)
Creates a memory client with automatic RPC response snapshotting.
Options:
fork.transport(required): Viem transport to fork fromfork.blockTag?: Block number to fork fromcommon?: Chain configurationtest.resolveSnapshotPath?: How to resolve snapshot paths (default:'vitest')'vitest': Automatic resolution using vitest context (places in__rpc_snapshots__/subdirectory)() => string: Custom function returning full absolute path to snapshot file
test.autosave?: When to save snapshots (default:'onRequest')'onRequest': Save after each cached request'onStop': Save when stopping the server'onSave': Save only when manually callingsaveSnapshots()
Returns:
- All
MemoryClientproperties server.http: HTTP server instanceserver.rpcUrl: URL of running serverserver.start(): Start the serverserver.stop(): Stop the server (auto-saves if autosave is'onStop')saveSnapshots(): Manually save snapshots
Example:
const client = createTestSnapshotClient({
fork: {
transport: http('https://mainnet.optimism.io'),
blockTag: 123456n
}
})
await client.server.start()
const block = await client.getBlock({ blockNumber: 123456n })
await client.server.stop()createTestSnapshotNode(options)
Creates a Tevm node with automatic RPC response snapshotting.
Options:
- Same as
createTestSnapshotClient, but acceptsTevmNodeOptions
Returns:
- All
TevmNodeproperties server: Server instance (same ascreateTestSnapshotClient)saveSnapshots(): Manually save snapshots
Example:
import { blockNumberProcedure } from '@tevm/actions'
const node = createTestSnapshotNode({
fork: { transport: http('https://mainnet.optimism.io') }
})
const result = await blockNumberProcedure(node)({
jsonrpc: '2.0',
method: 'eth_blockNumber',
id: 1,
params: []
})createTestSnapshotTransport(options)
Creates a transport with automatic RPC response snapshotting.
Options:
transport(required): Viem transport to wraptest.autosave?: When to save snapshots (default:'onRequest')
Returns:
request: EIP-1193 request functionserver: Server instance (same ascreateTestSnapshotClient)saveSnapshots(): Manually save snapshots
Example:
const transport = createTestSnapshotTransport({
transport: http('https://mainnet.optimism.io')
})
const result = await transport.request({
method: 'eth_getBlockByNumber',
params: ['0x123', false]
})Autosave Modes
'onRequest' (default)
Saves snapshots immediately after each cached request. Provides real-time persistence.
const client = createTestSnapshotClient({
fork: { transport: http('https://mainnet.optimism.io') },
test: { autosave: 'onRequest' } // default, can be omitted
})'onStop'
Saves snapshots only when server.stop() is called. Better performance for batch operations.
const client = createTestSnapshotClient({
fork: { transport: http('https://mainnet.optimism.io') },
test: { autosave: 'onStop' }
})
// No snapshots saved during these calls
await client.getBlock({ blockNumber: 1n })
await client.getBlock({ blockNumber: 2n })
// All snapshots saved here
await client.server.stop()'onSave'
No automatic saving. Complete manual control via saveSnapshots().
const client = createTestSnapshotClient({
fork: { transport: http('https://mainnet.optimism.io') },
test: { autosave: 'onSave' }
})
await client.getBlock({ blockNumber: 1n })
await client.server.stop() // Does not save
// Manually trigger save
await client.saveSnapshots() // Now savedSnapshot Location
Snapshots are automatically placed in a __rpc_snapshots__ subdirectory next to your test file:
src/
├── myTest.spec.ts
└── __rpc_snapshots__/
└── myTest.spec.ts.snap.jsonNo configuration needed - snapshot paths are resolved automatically using Vitest's test context.
Examples
Global Setup
// vitest.setup.ts
import { createTestSnapshotClient } from '@tevm/test-node'
import { http } from 'viem'
import { afterAll, beforeAll } from 'vitest'
export const client = createTestSnapshotClient({
fork: {
transport: http('https://mainnet.optimism.io'),
blockTag: 123456n
}
})
beforeAll(() => client.server.start())
afterAll(() => client.server.stop())// myTest.spec.ts
import { client } from './vitest.setup'
it('fetches block', async () => {
const block = await client.getBlock({ blockNumber: 123456n })
expect(block.number).toBe(123456n)
})Per-Test Client
import { createTestSnapshotClient } from '@tevm/test-node'
import { http } from 'viem'
it('works with local client', async () => {
const client = createTestSnapshotClient({
fork: { transport: http('https://mainnet.optimism.io') }
})
const block = await client.getBlock({ blockNumber: 1n })
expect(block.number).toBe(1n)
})Using with Viem Client
import { createMemoryClient } from '@tevm/memory-client'
import { createTestSnapshotTransport } from '@tevm/test-node'
import { http } from 'viem'
const transport = createTestSnapshotTransport({
transport: http('https://mainnet.optimism.io')
})
const client = createMemoryClient({
fork: { transport }
})
const block = await client.getBlock({ blockNumber: 1n })Custom Snapshot Path
import { createTestSnapshotClient } from '@tevm/test-node'
import { http } from 'viem'
import path from 'node:path'
const client = createTestSnapshotClient({
fork: { transport: http('https://mainnet.optimism.io') },
test: {
resolveSnapshotPath: () => path.join(process.cwd(), 'custom-snapshots', 'my-test.snap.json')
}
})
// Snapshots saved to custom-snapshots/my-test.snap.json