Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Testing Overview

What is Brane.Tester?

Brane.Tester is a specialized client for interacting with local test nodes like Anvil, Hardhat, and Ganache. It extends the standard Brane capabilities with test-specific operations that are only available on development networks.

import sh.brane.rpc.Brane;
 
// Connect to local Anvil with default test key
Brane.Tester tester = Brane.connectTest();

When to Use Brane.Tester

Use Brane.Tester when you need:

  • State snapshots: Save and restore chain state for test isolation
  • Account impersonation: Send transactions from any address without its private key
  • Time manipulation: Advance blockchain time for testing time-dependent logic
  • Account manipulation: Set balances, nonces, code, and storage directly
  • Mining control: Manual block mining, disable automine, configure intervals

Supported Test Nodes

NodeModeDocumentation
Anvil (Recommended)TestNodeMode.ANVILFoundry Anvil
Hardhat NetworkTestNodeMode.HARDHATHardhat Network
GanacheTestNodeMode.GANACHEGanache

Anvil is recommended for the best feature support and performance.

Quick Example

import sh.brane.rpc.Brane;
import sh.brane.rpc.SnapshotId;
import sh.brane.rpc.ImpersonationSession;
import sh.brane.core.types.Address;
import sh.brane.core.types.Wei;
import sh.brane.core.builder.TxBuilder;
 
public class TesterExample {
    public static void main(String[] args) {
        // 1. Connect to local Anvil
        Brane.Tester tester = Brane.connectTest();
 
        // 2. Take a snapshot before tests
        SnapshotId snapshot = tester.snapshot();
        try {
            // 3. Manipulate account state
            Address testAccount = new Address("0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
            tester.setBalance(testAccount, Wei.fromEther(new java.math.BigDecimal("1000")));
 
            // 4. Impersonate any address
            Address whale = new Address("0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8");
            tester.setBalance(whale, Wei.fromEther(new java.math.BigDecimal("10000")));
 
            try (ImpersonationSession session = tester.impersonate(whale)) {
                var request = TxBuilder.eip1559()
                    .to(testAccount)
                    .value(Wei.fromEther(new java.math.BigDecimal("100")))
                    .build();
                var receipt = session.sendTransactionAndWait(request);
                System.out.println("Transfer from whale: " + receipt.transactionHash());
            }
 
            // 5. Advance time (for testing vesting, locks, etc.)
            tester.increaseTime(86400); // 1 day in seconds
            tester.mine();
 
        } finally {
            // 6. Restore original state
            tester.revert(snapshot);
        }
    }
}

Creating a Tester Client

Default Connection (Local Anvil)

The simplest way to create a tester connects to Anvil at http://127.0.0.1:8545 with the default test key:

Brane.Tester tester = Brane.connectTest();

With Custom Signer

Use a specific Anvil test account:

import sh.brane.rpc.AnvilSigners;
 
// Use the 4th test account (index 3)
var signer = AnvilSigners.keyAt(3);
Brane.Tester tester = Brane.connectTest(signer);

With Custom URL

Connect to a remote or custom-port Anvil instance:

Brane.Tester tester = Brane.connectTest("http://192.168.1.100:8545");

With Different Test Node

Use Hardhat or Ganache instead of Anvil:

import sh.brane.rpc.TestNodeMode;
import sh.brane.core.crypto.PrivateKeySigner;
 
var signer = new PrivateKeySigner("0x...");
Brane.Tester tester = Brane.connectTest(
    "http://localhost:8545",
    signer,
    TestNodeMode.HARDHAT
);

Full Builder Configuration

Brane.Tester tester = Brane.builder()
    .rpcUrl("http://localhost:8545")
    .signer(AnvilSigners.defaultKey())
    .testMode(TestNodeMode.ANVIL)
    .retries(3)
    .buildTester();

Core Features

Snapshots and Revert

Snapshots save the entire chain state, allowing you to restore it later. This is essential for test isolation.

// Take snapshot at test start
SnapshotId snapshot = tester.snapshot();
try {
    // Run test operations that modify state
    tester.setBalance(address, Wei.fromEther(new java.math.BigDecimal("1000")));
    tester.sendTransactionAndWait(request);
} finally {
    // Always restore - ensures clean state for next test
    tester.revert(snapshot);
}

Account Impersonation

Impersonation lets you send transactions from any address without possessing its private key. This is invaluable for testing interactions with whale accounts, DAO contracts, or protocol admins.

Address whale = new Address("0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8");
 
// Fund the address first (impersonation doesn't create ETH)
tester.setBalance(whale, Wei.fromEther(new java.math.BigDecimal("10000")));
 
try (ImpersonationSession session = tester.impersonate(whale)) {
    var request = TxBuilder.eip1559()
        .to(recipient)
        .value(Wei.fromEther(new java.math.BigDecimal("1000")))
        .build();
 
    // Transaction sent as whale, without whale's private key
    var receipt = session.sendTransactionAndWait(request);
}
// Impersonation automatically stops when session closes

Time Manipulation

Test time-dependent logic like vesting schedules, lock periods, or auction deadlines:

// Advance by specific duration
tester.increaseTime(86400); // 1 day in seconds
tester.mine(); // Mine block with new timestamp
 
// Or set exact timestamp for next block
long futureTimestamp = System.currentTimeMillis() / 1000 + 30 * 86400; // 30 days from now
tester.setNextBlockTimestamp(futureTimestamp);
tester.mine();

Account State Manipulation

Directly modify account state without transactions:

// Set ETH balance
tester.setBalance(address, Wei.fromEther(new java.math.BigDecimal("1000")));
 
// Set nonce (useful for testing nonce-dependent logic)
tester.setNonce(address, 42);
 
// Deploy bytecode at address
tester.setCode(address, new HexData("0x608060405234..."));
 
// Set storage slot
Hash slot = new Hash("0x0000000000000000000000000000000000000000000000000000000000000000");
Hash value = new Hash("0x000000000000000000000000000000000000000000000000000000000000002a");
tester.setStorageAt(contractAddress, slot, value);

Mining Control

Control when and how blocks are produced:

// Mine single block
tester.mine();
 
// Mine multiple blocks
tester.mine(100);
 
// Mine with time interval between blocks
tester.mine(10, 12); // 10 blocks, 12 seconds apart
 
// Disable automine for batching transactions
tester.setAutomine(false);
Hash tx1 = tester.sendTransaction(request1);
Hash tx2 = tester.sendTransaction(request2);
tester.mine(); // Both transactions in same block
tester.setAutomine(true);
 
// Set interval mining (automatic mining at fixed intervals)
tester.setIntervalMining(12_000); // Mine every 12 seconds
tester.setIntervalMining(0); // Disable interval mining

Anvil Test Keys

Anvil starts with 10 pre-funded accounts (10,000 ETH each). Access them via AnvilSigners:

import sh.brane.rpc.AnvilSigners;
 
// Default key (account 0)
var signer = AnvilSigners.defaultKey();
 
// Specific account (0-9)
var signer3 = AnvilSigners.keyAt(3);
 
// Number of available accounts
int count = AnvilSigners.count(); // 10

Type Hierarchy

Brane.Tester is a sibling to Brane.Reader and Brane.Signer in the sealed type hierarchy:

// Exhaustive pattern matching
switch (client) {
    case Brane.Reader r  -> handleReadOnly(r);
    case Brane.Signer s  -> handleSigner(s);
    case Brane.Tester t  -> handleTester(t);
}
 
// Tester includes signing capabilities
Hash txHash = tester.sendTransaction(request);
TransactionReceipt receipt = tester.sendTransactionAndWait(request);
 
// Get a Signer view if needed
Brane.Signer signer = tester.asSigner();

Best Practices

Test Isolation with Snapshots

Always use snapshots to ensure tests don't affect each other:

class MyContractTest {
    private static Brane.Tester tester;
    private SnapshotId snapshot;
 
    @BeforeAll
    static void setup() {
        tester = Brane.connectTest();
    }
 
    @BeforeEach
    void createSnapshot() {
        snapshot = tester.snapshot();
    }
 
    @AfterEach
    void revertSnapshot() {
        tester.revert(snapshot);
    }
 
    @Test
    void testTransfer() {
        // Test code here - state will be reverted after
    }
}

Parallel Test Considerations

When running tests in parallel on the same Anvil instance:

  • Each test should take its own snapshot and revert in a finally block
  • Use distinct addresses for different tests to avoid nonce conflicts
  • Run time manipulation tests serially or on separate Anvil instances

Forking from Live Networks

Reset and fork from a live network for testing against real state:

// Fork mainnet at a specific block
tester.reset("https://eth-mainnet.alchemyapi.io/v2/YOUR_KEY", 18_000_000L);
 
// Now you can interact with mainnet state locally
BigInteger balance = tester.getBalance(uniswapRouter);

Prerequisites

To use Brane.Tester, you need a local test node running. Start Anvil with:

# Install Foundry if you haven't
curl -L https://foundry.paradigm.xyz | bash
foundryup
 
# Start Anvil
anvil

Anvil will start at http://127.0.0.1:8545 with 10 pre-funded test accounts.