Mock Transport
Copy
Ask AI
import { Effect, Schedule, Layer } from 'effect'
import { getBlockNumber, Provider, TestTransport, withTimeout, withoutCache } from 'voltaire-effect'
import { describe, it, expect } from 'vitest'
describe('Provider', () => {
it('returns mocked block number', async () => {
// Compose layers first
const TestLayer = Provider.pipe(
Layer.provide(TestTransport({ eth_blockNumber: () => '0x1234' }))
)
const program = Effect.gen(function* () {
return yield* getBlockNumber()
}).pipe(
withTimeout('5 seconds'),
Effect.provide(TestLayer)
)
expect(await Effect.runPromise(program)).toBe(4660n)
})
})
Mock Multiple Methods
Copy
Ask AI
import { Effect, Layer } from 'effect'
import { getBlockNumber, getBalance, getBlock, Provider, TestTransport, withoutCache } from 'voltaire-effect'
const mockTransport = TestTransport({
eth_blockNumber: () => '0x1234',
eth_getBalance: (params) => params[0] === '0xRich...' ? '0xde0b6b3a7640000' : '0x0',
eth_getBlock: () => ({ number: '0x1234', hash: '0x...', transactions: [] }),
eth_call: (params) => params[0]?.data?.startsWith('0x70a08231')
? '0x0000000000000000000000000000000000000000000000000000000005f5e100'
: '0x'
})
// Compose layers first, then provide once
const TestLayer = Layer.merge(Provider, mockTransport)
const program = Effect.gen(function* () {
return yield* Effect.all({
blockNumber: getBlockNumber(),
balance: getBalance('0xRich...', 'latest'),
block: getBlock({ blockTag: 'latest' })
})
}).pipe(withoutCache, Effect.provide(TestLayer))
Test Contract Reads
Copy
Ask AI
import { Effect, Layer } from 'effect'
import { Contract, Provider, TestTransport } from 'voltaire-effect'
const erc20Abi = [
{ type: 'function', name: 'balanceOf', inputs: [{ name: 'account', type: 'address' }], outputs: [{ type: 'uint256' }], stateMutability: 'view' }
] as const
it('reads balance', async () => {
// Compose layers first
const TestLayer = Provider.pipe(
Layer.provide(TestTransport({
eth_call: () => '0x0000000000000000000000000000000000000000000000056bc75e2d63100000'
}))
)
const program = Effect.gen(function* () {
const token = yield* Contract('0xToken...', erc20Abi)
return yield* token.read.balanceOf('0xUser...')
}).pipe(Effect.provide(TestLayer))
expect(await Effect.runPromise(program)).toBe(100000000000000000000n)
})
Test Error Handling
Copy
Ask AI
import { Effect, Exit, Layer } from 'effect'
import { getBalance, Provider, TestTransport } from 'voltaire-effect'
it('handles RPC errors', async () => {
// Compose layers first
const TestLayer = Provider.pipe(
Layer.provide(TestTransport({ eth_getBalance: () => { throw new Error('Rate limited') } }))
)
const program = Effect.gen(function* () {
return yield* getBalance('0x...', 'latest')
}).pipe(Effect.provide(TestLayer))
const exit = await Effect.runPromiseExit(program)
expect(Exit.isFailure(exit)).toBe(true)
})
it('recovers from errors', async () => {
// Compose layers first
const TestLayer = Provider.pipe(
Layer.provide(TestTransport({ eth_getBalance: () => { throw new Error('Failed') } }))
)
const program = Effect.gen(function* () {
return yield* getBalance('0x...', 'latest')
}).pipe(
Effect.catchTag('TransportError', () => Effect.succeed(0n)),
Effect.provide(TestLayer)
)
expect(await Effect.runPromise(program)).toBe(0n)
})
Test Crypto
Each crypto module exports test layers for deterministic mocking:Copy
Ask AI
import { hash, KeccakTest } from 'voltaire-effect/crypto/Keccak256'
it('hashes data', async () => {
const result = await Effect.runPromise(
hash(new Uint8Array([1, 2, 3])).pipe(Effect.provide(KeccakTest))
)
expect(result).toBeInstanceOf(Uint8Array)
expect(result.length).toBe(32)
})
All Crypto Services
UseCryptoLive and CryptoTest convenience layers to provide all crypto services at once:
Copy
Ask AI
import { CryptoLive, CryptoTest } from 'voltaire-effect'
import { KeccakService } from 'voltaire-effect/crypto/Keccak256'
import { Secp256k1Service } from 'voltaire-effect/crypto/Secp256k1'
// Production - all crypto services in one layer
const program = Effect.gen(function* () {
const keccak = yield* KeccakService
const secp = yield* Secp256k1Service
// All crypto services available
}).pipe(Effect.provide(CryptoLive))
// Testing - deterministic mocks for all crypto
const testProgram = program.pipe(Effect.provide(CryptoTest))
Integration Test (Anvil)
Copy
Ask AI
import { Effect, Layer, Schedule } from 'effect'
import {
SignerService, Signer, LocalAccount, Provider, HttpTransport,
getBalance, waitForTransactionReceipt
} from 'voltaire-effect'
import { Hex } from '@tevm/voltaire'
const TestLayer = Layer.mergeAll(
Provider,
Signer.Live,
LocalAccount(Hex.fromHex('0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80')),
HttpTransport({
url: 'http://localhost:8545',
timeout: '10 seconds',
retrySchedule: Schedule.recurs(1)
})
)
it('sends transaction', async () => {
const program = Effect.gen(function* () {
const signer = yield* SignerService
const before = yield* getBalance('0x70997970C51812dc3A010C7d01b50e0d17dc79C8', 'latest')
const txHash = yield* signer.sendTransaction({ to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', value: 1000000000000000000n })
yield* waitForTransactionReceipt(txHash, { confirmations: 1, timeout: '30 seconds' })
const after = yield* getBalance('0x70997970C51812dc3A010C7d01b50e0d17dc79C8', 'latest')
return after - before
}).pipe(Effect.provide(TestLayer))
expect(await Effect.runPromise(program)).toBe(1000000000000000000n)
})
Snapshot Testing
Copy
Ask AI
import * as Address from 'voltaire-effect/primitives/Address'
import * as S from 'effect/Schema'
import * as Effect from 'effect/Effect'
import { KeccakLive } from 'voltaire-effect/crypto/Keccak256'
it('checksums correctly', async () => {
const addr = S.decodeSync(Address.Hex)('0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed')
const program = S.encode(Address.Checksummed)(addr).pipe(Effect.provide(KeccakLive))
expect(await Effect.runPromise(program)).toMatchInlineSnapshot('"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"')
})
Property-Based Testing
Copy
Ask AI
import * as Address from 'voltaire-effect/primitives/Address'
import * as S from 'effect/Schema'
import * as fc from 'fast-check'
it('round-trips through hex', async () => {
await fc.assert(fc.asyncProperty(
fc.hexaString({ minLength: 40, maxLength: 40 }),
async (hex) => {
const addr = S.decodeSync(Address.Hex)(`0x${hex}`)
const encoded = S.encodeSync(Address.Hex)(addr)
const roundTripped = S.decodeSync(Address.Hex)(encoded)
expect(Address.equals(addr, roundTripped)).toBe(true)
}
))
})
See Also
- Testing Guide — Comprehensive testing patterns
- Debugging — Debug Effect programs
- Crypto — CryptoLive and CryptoTest layers
- Error Handling Example — Test error paths
- Effect Testing — Official Effect testing guide

