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
| Node | Mode | Documentation |
|---|---|---|
| Anvil (Recommended) | TestNodeMode.ANVIL | Foundry Anvil |
| Hardhat Network | TestNodeMode.HARDHAT | Hardhat Network |
| Ganache | TestNodeMode.GANACHE | Ganache |
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 closesTime 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 miningAnvil 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(); // 10Type 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
anvilAnvil will start at http://127.0.0.1:8545 with 10 pre-funded test accounts.