Testing Basics
8 examples to get you started with Solana program testing - 5 basic and 3 intermediate. Covers the testing pyramid: LiteSVM 0.6.x unit tests, Mollusk instruction tests, Anchor TS integration, and validator E2E.
Prerequisites
- Anchor 0.32.1 workspace with
anchor testconfigured. - Rust 1.91.1 toolchain.
- Optional:
@solana/kit7.0.0 for client-side tests.
anchor --version # 0.32.1
cargo test --versionBasic Examples
1. Run Anchor Tests
Default integration test spins local validator.
anchor test- Compiles program, deploys to local validator, runs
tests/*.ts. - Uses provider cluster from
Anchor.toml. - Slower than LiteSVM but exercises full RPC path.
Related: Anchor TS Tests
2. LiteSVM Smoke Test
In-process SVM without validator process.
use litesvm::LiteSVM;
use solana_sdk::{signature::Keypair, signer::Signer, transaction::Transaction};
let mut svm = LiteSVM::new();
let payer = Keypair::new();
svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();- Sub-millisecond iteration for instruction logic.
- Load
.sowithsvm.add_program. - Ideal for security negative tests.
Related: LiteSVM
3. Mollusk Instruction Harness
Lightweight instruction-level runner in Rust tests.
use mollusk_svm::Mollusk;
use solana_sdk::instruction::{AccountMeta, Instruction};
let mollusk = Mollusk::new(&program_id, "target/deploy/my_program");
let ix = Instruction { program_id, accounts: vec![], data: vec![] };
mollusk.process_instruction(&ix, &accounts);- Faster setup than full Anchor suite for pure Rust devs.
- Good middle layer between LiteSVM and validator.
Related: Mollusk
4. Assert Program Error
Negative tests are mandatory for security.
try {
await program.methods.badWithdraw().rpc();
throw new Error("expected fail");
} catch (e) {
// expect anchor error code
}- Match error codes from
#[error_code]enum. - Test missing signer, wrong mint, stale oracle paths.
- One test per catalog attack class.
Related: Sealevel Attacks Catalog
5. Reset Local Validator in CI
Deterministic CI requires clean ledger.
solana-test-validator --reset --quiet &
anchor test --skip-local-validator--resetclears state between runs.- Pin Agave 4.1.1 validator binary in CI image.
- Kill orphaned validators on port 8899.
Related: Testing Against a Validator
Intermediate Examples
6. Test PDA Derivation
Client and program must agree on seeds.
const [pda] = PublicKey.findProgramAddressSync(
[Buffer.from("vault"), user.toBuffer()],
program.programId
);
const account = await program.account.vault.fetch(pda);- Compare bump stored on-chain with client derivation.
- Fail tests if seed strings mismatch (
b"vault"vs"vault").
Related: Testing CPIs & PDAs
7. Surfpool Fork Integration
DeFi tests need mainnet pool state.
surfpool start --rpc-url https://api.mainnet-beta.solana.com
export ANCHOR_PROVIDER_URL=http://127.0.0.1:8899
anchor test --skip-local-validator- Surfpool 0.12.0 overlays fork state for realistic CPI chains.
- Use for Jupiter/AMM integration tests only when needed.
8. Fuzz Deposit Bounds
Property tests find overflow edges.
cargo fuzz run deposit_fuzz -- -max_total_time=300- Trident/fuzz targets random amounts and account flags.
- Run extended fuzz before audit (Fuzzing Programs).
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.