Skip to main content
Effect’s service model makes testing straightforward. Swap production layers for test layers.
See the Effect Testing Guide for comprehensive testing patterns.

Testing Schema Validation

Schema decode is pure. Test directly:
import * as S from 'effect/Schema'
import * as Address from 'voltaire-effect/primitives/Address'
import { describe, it, expect } from 'vitest'

describe('Address schema', () => {
  it('decodes valid hex', () => {
    const addr = S.decodeSync(Address.Hex)(
      '0x742d35Cc6634C0532925a3b844Bc9e7595f251e3'
    )
    expect(addr.length).toBe(20)
  })

  it('rejects invalid hex', () => {
    expect(() => S.decodeSync(Address.Hex)('invalid')).toThrow()
  })
})

Testing with Either

Use decodeEither to test error cases without throwing:
import * as S from 'effect/Schema'
import * as Either from 'effect/Either'

it('returns Left for invalid input', () => {
  const result = S.decodeEither(Address.Hex)('0x123')
  expect(Either.isLeft(result)).toBe(true)
})

Mocking Services

Use TestTransport to mock JSON-RPC responses:
import * as Effect from 'effect/Effect'
import * as Layer from 'effect/Layer'
import { TestTransport, Provider, getBalance } from 'voltaire-effect'

const TestLayer = Provider.pipe(
  Layer.provide(TestTransport({
    'eth_getBalance': '0xde0b6b3a7640000', // 1 ETH in hex
    'eth_blockNumber': '0x1234',
    'eth_chainId': '0x1'
  }))
)
Use free functions with test layers:
import * as Effect from 'effect/Effect'
import * as Layer from 'effect/Layer'
import { getBalance, TestTransport, Provider } from 'voltaire-effect'

it('fetches balance', async () => {
  const program = getBalance('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')

  const TestLayer = Provider.pipe(
    Layer.provide(TestTransport({ 'eth_getBalance': '0xde0b6b3a7640000' }))
  )

  const result = await Effect.runPromise(
    program.pipe(Effect.provide(TestLayer))
  )

  expect(result).toBe(1000000000000000000n)
})

Testing Error Paths

Mock errors using TransportError in TestTransport:
import * as Effect from 'effect/Effect'
import * as Layer from 'effect/Layer'
import { TestTransport, Provider, TransportError, getBalance } from 'voltaire-effect'

const FailingLayer = Provider.pipe(
  Layer.provide(TestTransport({
    'eth_getBalance': new TransportError({ code: -32000, message: 'timeout' }),
    'eth_blockNumber': new TransportError({ code: -32000, message: 'timeout' })
  }))
)

it('handles provider errors', async () => {
  const program = getBalance('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045').pipe(
    Effect.catchTag('TransportError', () => Effect.succeed(0n))
  )

  const result = await Effect.runPromise(
    program.pipe(Effect.provide(FailingLayer))
  )

  expect(result).toBe(0n)
})

Testing Crypto

Use CryptoTest layer for deterministic crypto outputs:
import * as Effect from 'effect/Effect'
import { CryptoTest, keccak256 } from 'voltaire-effect'

it('uses keccak service', async () => {
  const program = keccak256(new Uint8Array([1, 2, 3]))

  const result = await Effect.runPromise(
    program.pipe(Effect.provide(CryptoTest))
  )

  // CryptoTest returns deterministic outputs
  expect(result.length).toBe(32)
})

CryptoLive and CryptoTest

Use CryptoLive for production and CryptoTest for deterministic unit tests:
import * as Effect from 'effect/Effect'
import { CryptoLive, CryptoTest, keccak256, signMessage } from 'voltaire-effect'

// CryptoLive bundles: Keccak256, Secp256k1, SHA256, Blake2, Ripemd160,
// BLS12-381, Ed25519, P256, KZG, HDWallet, BN254, BIP-39, HMAC, EIP-712,
// ChaCha20Poly1305, Keystore

const program = Effect.gen(function* () {
  const hash = yield* keccak256(new Uint8Array([1, 2, 3]))
  return hash
})

// Production
await Effect.runPromise(program.pipe(Effect.provide(CryptoLive)))

// Testing (deterministic outputs)
await Effect.runPromise(program.pipe(Effect.provide(CryptoTest)))

Composing Test Layers

Combine transport and crypto test layers:
import * as Effect from 'effect/Effect'
import * as Layer from 'effect/Layer'
import { TestTransport, Provider, CryptoTest, getBalance, keccak256 } from 'voltaire-effect'

const TestLayer = Layer.mergeAll(
  Provider.pipe(Layer.provide(TestTransport({ 'eth_getBalance': '0x0' }))),
  CryptoTest
)

const program = Effect.gen(function* () {
  const balance = yield* getBalance('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')
  const hash = yield* keccak256(new Uint8Array([1, 2, 3]))
  return { balance, hash }
})

const result = await Effect.runPromise(
  program.pipe(Effect.provide(TestLayer))
)

Integration Tests

Use live layers for integration tests:
import * as Effect from 'effect/Effect'
import * as Layer from 'effect/Layer'
import { HttpTransport, Provider, CryptoLive, getBlock, getBalance } from 'voltaire-effect'

const IntegrationLayer = Layer.mergeAll(
  Provider.pipe(Layer.provide(HttpTransport('https://eth.llamarpc.com'))),
  CryptoLive
)

it('fetches real block', async () => {
  const program = getBlock('latest')

  const block = await Effect.runPromise(
    program.pipe(Effect.provide(IntegrationLayer))
  )

  expect(block.number).toBeGreaterThan(0n)
})

See Also