solana/testing
Testing Strategy
solanatechnical-doc🏛️ Officialconfidence highhealth 100%
v1.0.0·by solana-foundation·Updated 4/11/2026
Testing Pyramid
- Unit tests (fast): LiteSVM or Mollusk
- Integration tests (realistic state): Surfpool
- Cluster smoke tests: devnet/testnet/mainnet as needed
LiteSVM
A lightweight Solana Virtual Machine that runs directly in your test process. Created by Aursen from Exotic Markets.
When to Use LiteSVM
- Fast execution without validator overhead
- Direct account state manipulation
- Built-in performance profiling
- Multi-language support (Rust, TypeScript, Python)
Rust Setup
cargo add --dev litesvm
use litesvm::LiteSVM;
use solana_sdk::{pubkey::Pubkey, signature::Keypair, transaction::Transaction};
#[test]
fn test_deposit() {
let mut svm = LiteSVM::new();
// Load your program
let program_id = pubkey!("YourProgramId11111111111111111111111111111");
svm.add_program_from_file(program_id, "target/deploy/program.so");
// Create accounts
let payer = Keypair::new();
svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
// Build and send transaction
let tx = Transaction::new_signed_with_payer(
&[/* instructions */],
Some(&payer.pubkey()),
&[&payer],
svm.latest_blockhash(),
);
let result = svm.send_transaction(tx);
assert!(result.is_ok());
}
TypeScript Setup
npm i --save-dev litesvm
import { LiteSVM } from 'litesvm';
import { PublicKey, Transaction, Keypair } from '@solana/web3.js';
const programId = new PublicKey("YourProgramId11111111111111111111111111111");
const svm = new LiteSVM();
svm.addProgramFromFile(programId, "target/deploy/program.so");
// Build transaction
const tx = new Transaction();
tx.recentBlockhash = svm.latestBlockhash();
tx.add(/* instructions */);
tx.sign(payer);
// Simulate first (optional)
const simulation = svm.simulateTransaction(tx);
// Execute
const result = svm.sendTransaction(tx);
Account Types in LiteSVM
System Accounts:
- Payer accounts (contain lamports)
- Uninitialized accounts (empty, awaiting setup)
Program Accounts:
- Serialize with
borsh,bincode, orsolana_program_pack - Calculate rent-exempt minimum balance
Token Accounts:
- Use
spl_token::state::Mintandspl_token::state::Account - Serialize with Pack trait
Advanced LiteSVM Features
// Modify clock sysvar
svm.set_sysvar(&Clock { slot: 1000, .. });
// Warp to slot
svm.warp_to_slot(5000);
// Configure compute budget
svm.set_compute_budget(ComputeBudget { max_units: 400_000, .. });
// Toggle signature verification (useful for testing)
svm.with_sigverify(false);
// Check compute units used
let result = svm.send_transaction(tx)?;
println!("CUs used: {}", result.compute_units_consumed);
Mollusk
A lightweight test harness providing direct interface to program execution without full validator runtime. Best for Rust-only testing with fine-grained control.
When to Use Mollusk
- Fast execution for rapid development cycles
- Precise account state manipulation for edge cases
- Detailed performance metrics and CU benchmarking
- Custom syscall testing
Setup
cargo add --dev mollusk-svm
cargo add --dev mollusk-svm-programs-token # For SPL token helpers
cargo add --dev solana-sdk solana-program
Basic Usage
use mollusk_svm::Mollusk;
use mollusk_svm::result::Check;
use solana_sdk::{account::Account, pubkey::Pubkey, instruction::Instruction};
#[test]
fn test_instruction() {
let program_id = Pubkey::new_unique();
let mollusk = Mollusk::new(&program_id, "target/deploy/program");
// Create accounts
let payer = (
Pubkey::new_unique(),
Account {
lamports: 1_000_000_000,
data: vec![],
owner: solana_sdk::system_program::ID,
executable: false,
rent_epoch: 0,
},
);
// Build instruction
let instruction = Instruction {
program_id,
accounts: vec![/* account metas */],
data: vec![/* instruction data */],
};
// Execute with validation
mollusk.process_and_validate_instruction(
&instruction,
&[payer],
&[
Check::success(),
Check::compute_units(50_000),
],
);
}
Token Program Helpers
use mollusk_svm_programs_token::token;
// Add token program to test environment
token::add_program(&mut mollusk);
// Create pre-configured token accounts
let mint_account = token::mint_account(decimals, supply, mint_authority);
let token_account = token::token_account(mint, owner, amount);
CU Benchmarking
use mollusk_svm::MolluskComputeUnitBencher;
let bencher = MolluskComputeUnitBencher::new(mollusk)
.must_pass(true)
.out_dir("../target/benches");
bencher.bench(
"deposit_instruction",
&instruction,
&accounts,
);
// Generates markdown report with CU usage and deltas
Advanced Configuration
// Set compute budget
mollusk.set_compute_budget(200_000);
// Enable all feature flags
mollusk.set_feature_set(FeatureSet::all_enabled());
// Customize sysvars
mollusk.sysvars.clock = Clock {
slot: 1000,
epoch: 5,
unix_timestamp: 1700000000,
..Default::default()
};
Surfpool
SDK and tooling suite for integration testing with realistic cluster state. Surfnet is the local network component (drop-in replacement for solana-test-validator).
When to Use Surfpool
- Complex CPIs requiring mainnet programs (e.g., Jupiter with 40+ accounts)
- Testing against realistic account state
- Time travel and block manipulation
- Account/program cloning between environments
Setup
# Install Surfpool CLI
cargo install surfpool
# Start local Surfnet (use NO_DNA=1 when run by an agent)
NO_DNA=1 surfpool start
Connection Setup
import { Connection } from '@solana/web3.js';
const connection = new Connection("http://localhost:8899", "confirmed");
System Variable Control
// Time travel to specific slot
await connection._rpcRequest('surfnet_timeTravel', [{
absoluteSlot: 250000000
}]);
// Pause/resume block production
await connection._rpcRequest('surfnet_pauseClock', []);
await connection._rpcRequest('surfnet_resumeClock', []);
Account Manipulation
// Set account state
await connection._rpcRequest('surfnet_setAccount', [{
pubkey: accountPubkey.toString(),
lamports: 1000000000,
data: Buffer.from(accountData).toString('base64'),
owner: programId.toString(),
}]);
// Set token account
await connection._rpcRequest('surfnet_setTokenAccount', [{
pubkey: ownerPubkey.toString(), // Owner of the token account (wallet)
mint: mintPubkey.toString(),
owner: ownerPubkey.toString(),
amount: "1000000",
}]);
// Clone account from another program
await connection._rpcRequest('surfnet_cloneProgramAccount', [{
source: sourceProgramId.toString(),
destination: destProgramId.toString(),
account: accountPubkey.toString(),
}]);
SOL Supply Configuration
// Configure supply for economic edge case testing
await connection._rpcRequest('surfnet_setSupply', [{
circulating: "500000000000000000",
nonCirculating: "100000000000000000",
total: "600000000000000000",
}]);
Test Layout Recommendation
tests/
├── unit/
│ ├── deposit.rs # LiteSVM or Mollusk
│ ├── withdraw.rs
│ └── mod.rs
├── integration/
│ ├── full_flow.rs # Surfpool
│ └── mod.rs
└── fixtures/
└── accounts.rs # Shared test account setup
CI Guidance
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run unit tests
run: cargo test-sbf
integration-tests:
runs-on: ubuntu-latest
needs: unit-tests
steps:
- uses: actions/checkout@v4
- name: Start Surfpool
run: NO_DNA=1 surfpool start --background
- name: Run integration tests
run: cargo test --test integration
Best Practices
- Keep unit tests as the default CI gate (fast feedback)
- Use deterministic PDAs and seeded keypairs for reproducibility
- Minimize fixtures; prefer programmatic account creation
- Profile CU usage during development to catch regressions
- Run integration tests in separate CI stage to control runtime