Mollusk is a lightweight test harness for Solana programs written in Rust. It provides a simple interface for testing Solana program executions in a minified Solana Virtual Machine (SVM) environment.
Mollusk is designed for Rust-only testing. For multi-language testing, see [LiteSVMtesting/litesvm).
Overview
Mollusk takes a minimalist approach to testing:
mollusk.process_and_validate_instruction(
&instruction, // Instruction to test
&accounts, // Account states
&checks, // Assertions to verify
);
It creates a minimal SVM environment by provisioning:
- Program cache
- Transaction context
- Invoke context
These components directly execute your program’s ELF using the BPF Loader, without the overhead of a full validator.
Key Features
- Exceptionally Fast - No validator overhead, direct program execution
- Explicit Account Management - You provide all accounts needed
- Configurable Environment - Adjust compute budget, features, and sysvars
- Built-in Assertions - Ergonomic
Check API for validating results
- Instruction Chains - Test sequences of instructions
- Compute Unit Benchmarking - Built-in CU usage tracking
When to Use Mollusk
Mollusk is ideal for:
- Pure Rust testing - When your test suite is entirely in Rust
- Unit tests - Testing individual instructions in isolation
- Compute unit optimization - Benchmarking and tracking CU usage
- Fast CI/CD - Minimal dependencies and fast compilation
- Anchor programs - Testing with or without Anchor test templates
Installation
Add Mollusk to your test dependencies:
[dev-dependencies]
mollusk-svm = "0.1"
You can also initialize an Anchor project with Mollusk tests:
anchor init --test-template mollusk my-program
Basic Usage
Single Instruction Test
Here’s a simple test that processes one instruction:
use mollusk_svm::Mollusk;
use solana_sdk::{
account::Account,
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};
#[test]
fn test_initialize() {
let program_id = Pubkey::new_unique();
let key1 = Pubkey::new_unique();
let key2 = Pubkey::new_unique();
// Create the instruction
let instruction = Instruction::new_with_bytes(
program_id,
&[0], // instruction data
vec![
AccountMeta::new(key1, false),
AccountMeta::new_readonly(key2, false),
],
);
// Set up account states
let accounts = vec![
(key1, Account::default()),
(key2, Account::default()),
];
// Create Mollusk instance with your program
let mollusk = Mollusk::new(&program_id, "my_program");
// Execute the instruction
let result = mollusk.process_instruction(&instruction, &accounts);
assert!(result.result.is_ok());
}
With Validation Checks
Mollusk provides a Check API for ergonomic result validation:
use mollusk_svm::{Mollusk, result::Check};
use solana_sdk::{
account::Account,
instruction::Instruction,
pubkey::Pubkey,
system_instruction,
system_program,
};
#[test]
fn test_transfer_with_checks() {
let sender = Pubkey::new_unique();
let recipient = Pubkey::new_unique();
let base_lamports = 100_000_000u64;
let transfer_amount = 42_000u64;
// Create transfer instruction
let instruction = system_instruction::transfer(
&sender,
&recipient,
transfer_amount,
);
// Set up accounts
let accounts = [
(
sender,
Account::new(base_lamports, 0, &system_program::id()),
),
(
recipient,
Account::new(base_lamports, 0, &system_program::id()),
),
];
// Define checks
let checks = vec![
Check::success(),
Check::account(&sender)
.lamports(base_lamports - transfer_amount)
.build(),
Check::account(&recipient)
.lamports(base_lamports + transfer_amount)
.build(),
];
// Execute with validation
Mollusk::default().process_and_validate_instruction(
&instruction,
&accounts,
&checks,
);
}
Mollusk::default() creates an instance without loading custom programs, but includes default builtin programs. Use Mollusk::new() to load your program.
Testing Anchor Programs
When testing Anchor programs, you’ll work with the compiled program binary:
use anchor_lang::prelude::*;
use mollusk_svm::{Mollusk, result::Check};
use solana_sdk::{
account::Account,
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
system_program,
};
#[test]
fn test_anchor_initialize() {
// Your Anchor program ID
let program_id = Pubkey::new_from_array([
/* program ID bytes */
]);
// Create Mollusk with your program
let mollusk = Mollusk::new(&program_id, "my_anchor_program");
// Set up accounts
let user = Pubkey::new_unique();
let my_account = Pubkey::new_unique();
let accounts = vec![
(
my_account,
Account::new(
0,
8 + 32, // discriminator + data
&program_id,
),
),
(
user,
Account::new(1_000_000_000, 0, &system_program::id()),
),
(
system_program::id(),
Account::default(),
),
];
// Create instruction with Anchor discriminator
let mut instruction_data = vec![];
instruction_data.extend_from_slice(
&anchor_lang::idl::IdlInstruction::discriminator("initialize")
);
let instruction = Instruction::new_with_bytes(
program_id,
&instruction_data,
vec![
AccountMeta::new(my_account, false),
AccountMeta::new(user, true),
AccountMeta::new_readonly(system_program::id(), false),
],
);
// Execute and validate
let checks = vec![
Check::success(),
Check::account(&my_account)
.owner(&program_id)
.build(),
];
mollusk.process_and_validate_instruction(
&instruction,
&accounts,
&checks,
);
}
Instruction Chains
Mollusk can process sequences of instructions, useful for testing workflows:
use mollusk_svm::Mollusk;
use solana_sdk::{
system_instruction,
pubkey::Pubkey,
};
#[test]
fn test_transfer_chain() {
let mollusk = Mollusk::default();
let alice = Pubkey::new_unique();
let bob = Pubkey::new_unique();
let carol = Pubkey::new_unique();
let dave = Pubkey::new_unique();
let starting_lamports = 500_000_000;
// Execute chain of transfers
mollusk.process_instruction_chain(
&[
system_instruction::transfer(&alice, &bob, 100_000_000),
system_instruction::transfer(&bob, &carol, 50_000_000),
system_instruction::transfer(&bob, &dave, 50_000_000),
],
&[
(alice, system_account_with_lamports(starting_lamports)),
(bob, system_account_with_lamports(starting_lamports)),
(carol, system_account_with_lamports(starting_lamports)),
(dave, system_account_with_lamports(starting_lamports)),
],
);
}
fn system_account_with_lamports(lamports: u64) -> solana_sdk::account::Account {
solana_sdk::account::Account::new(
lamports,
0,
&solana_sdk::system_program::id(),
)
}
Validated Instruction Chains
You can validate state after each instruction in a chain:
use mollusk_svm::{Mollusk, result::Check};
#[test]
fn test_validated_chain() {
let mollusk = Mollusk::default();
let alice = Pubkey::new_unique();
let bob = Pubkey::new_unique();
let starting_lamports = 500_000_000;
let transfer_amount = 100_000_000;
mollusk.process_and_validate_instruction_chain(
&[
(
&system_instruction::transfer(&alice, &bob, transfer_amount),
&[
Check::success(),
Check::account(&alice)
.lamports(starting_lamports - transfer_amount)
.build(),
Check::account(&bob)
.lamports(starting_lamports + transfer_amount)
.build(),
],
),
// More instructions with checks...
],
&[
(alice, system_account_with_lamports(starting_lamports)),
(bob, system_account_with_lamports(starting_lamports)),
],
);
}
Instruction chains in Mollusk are not equivalent to Solana transactions. They don’t enforce transaction constraints like account limits or size restrictions.
Compute Unit Benchmarking
Mollusk includes a built-in bencher for tracking compute unit usage:
use mollusk_svm_bencher::MolluskComputeUnitBencher;
use mollusk_svm::Mollusk;
#[test]
fn bench_compute_units() {
// Optionally disable logging
solana_logger::setup_with("");
let program_id = Pubkey::new_unique();
let mollusk = Mollusk::new(&program_id, "my_program");
// Set up your instructions and accounts
let instruction0 = /* ... */;
let accounts0 = /* ... */;
let instruction1 = /* ... */;
let accounts1 = /* ... */;
// Run benchmarks
MolluskComputeUnitBencher::new(mollusk)
.bench(("initialize", &instruction0, &accounts0))
.bench(("process", &instruction1, &accounts1))
.must_pass(true) // Panic if any test fails
.out_dir("../target/benches")
.execute();
}
This generates a markdown file with compute unit usage:
| Name | CUs | Delta |
| ---------- | ----- | ------ |
| initialize | 450 | -- |
| process | 1,204 | +754 |
To run the benchmark:
Add this to your Cargo.toml:
[[bench]]
name = "compute_units"
harness = false
Check API Reference
The Check enum provides several validation methods:
// Success/failure checks
Check::success();
Check::err(program_error);
// Account state checks
Check::account(&pubkey)
.lamports(amount)
.data(&expected_data)
.owner(&program_id)
.executable(true)
.build();
// Compute unit checks
Check::compute_units(expected_units);
// Log checks
Check::logs_contain("Expected log message");
Advanced Configuration
Compute Budget
let mollusk = Mollusk::new(&program_id, "my_program")
.with_compute_budget(1_400_000); // Set max CUs
Feature Set
let mut mollusk = Mollusk::new(&program_id, "my_program");
mollusk.feature_set.activate(&feature_id, 0);
Sysvars
use solana_sdk::clock::Clock;
let mut mollusk = Mollusk::new(&program_id, "my_program");
let mut clock = Clock::default();
clock.unix_timestamp = 1234567890;
mollusk.sysvars.set_clock(clock);
Testing Best Practices
- Test one instruction at a time - Keep tests focused and isolated
- Use descriptive test names - Make test failures easy to understand
- Set up minimal accounts - Only include accounts needed for the test
- Validate expected outcomes - Use
Check API for comprehensive validation
- Benchmark critical paths - Track compute units for expensive operations
- Test error cases - Verify your program fails correctly
Example: Full Anchor Test Suite
use anchor_lang::prelude::*;
use mollusk_svm::{Mollusk, result::Check};
use solana_sdk::{
account::Account,
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};
mod tests {
use super::*;
fn setup() -> (Mollusk, Pubkey) {
let program_id = Pubkey::new_unique();
let mollusk = Mollusk::new(&program_id, "my_program");
(mollusk, program_id)
}
#[test]
fn test_initialize_success() {
let (mollusk, program_id) = setup();
// Test implementation
// ...
}
#[test]
fn test_initialize_already_initialized() {
let (mollusk, program_id) = setup();
// Test error case
// ...
}
#[test]
fn test_update_authorized() {
let (mollusk, program_id) = setup();
// Test update logic
// ...
}
}
Limitations
- Rust only - No TypeScript or Python support
- Explicit accounts - Must provide all accounts manually
- No RPC - Direct program execution only
- Minimal validator behavior - Doesn’t simulate full validator
Comparison with Other Frameworks
| Feature | Mollusk | LiteSVM | solana-program-test |
|---|
| Language | Rust only | Rust, TS, Python | Rust only |
| Speed | Fastest | Very fast | Fast |
| Setup | Minimal | Minimal | Moderate |
| Account Management | Manual | Automatic | Automatic |
| Assertions | Built-in Check | Manual | Manual |
| Best For | Rust unit tests | Multi-lang testing | Rust integration |
Next Steps
- Explore [LiteSVMtesting/litesvm) for multi-language testing
- Read [Testing Overviewtesting/overview) for testing strategies
- Check out Mollusk GitHub for more examples
- Learn about [Compute Unitsreferences/compute-units) optimization