The testutils module provides utilities for testing Soroban smart contracts, including test environment manipulation, snapshot management, and event inspection.
This module is only available when the testutils feature is enabled.
Overview
The testutils module enables comprehensive testing of smart contracts by providing:
- Environment state manipulation (ledger, storage, events)
- Mock authentication and authorization
- Snapshot-based testing
- Budget and cost tracking
- Address and key generation
Traits
Register
Register contracts for testing.
pub trait Register {
fn register<'i, I, A>(self, env: &Env, id: I, args: A) -> Address
where
I: Into<Option<&'i Address>>,
A: ConstructorArgs;
}
Example:
use soroban_sdk::{Env, testutils::Register};
#[test]
fn test_contract() {
let env = Env::default();
// Register with generated address
let contract_id = Contract.register(&env, None, ());
// Register with specific address
let address = Address::generate(&env);
let contract_id = Contract.register(&env, Some(&address), ());
}
Ledger
Test utilities for ledger state manipulation.
pub trait Ledger {
fn set(&self, ledger_info: LedgerInfo);
fn set_sequence_number(&self, sequence_number: u32);
fn set_timestamp(&self, timestamp: u64);
fn set_network_id(&self, network_id: [u8; 32]);
// ... more methods
}
Example:
use soroban_sdk::{Env, testutils::Ledger};
#[test]
fn test_time_based_logic() {
let env = Env::default();
// Set ledger timestamp
env.ledger().set_timestamp(1234567890);
// Set sequence number
env.ledger().set_sequence_number(100);
// Test contract
let contract_id = env.register(Contract, ());
let client = ContractClient::new(&env, &contract_id);
client.time_sensitive_function();
}
Events
Test utilities for inspecting contract events.
pub trait Events {
fn all(&self) -> ContractEvents;
}
Example:
use soroban_sdk::{Env, testutils::Events};
#[test]
fn test_events() {
let env = Env::default();
let contract_id = env.register(Contract, ());
let client = ContractClient::new(&env, &contract_id);
// Call function that emits events
client.transfer(&from, &to, &100);
// Get all events
let events = env.events().all();
assert_eq!(events.len(), 1);
// Filter by contract
let contract_events = events.filter_by_contract(&contract_id);
}
Logs
Test utilities for inspecting diagnostic logs.
pub trait Logs {
fn all(&self) -> Vec<String>;
fn print(&self);
}
Example:
use soroban_sdk::{Env, testutils::Logs};
#[test]
fn test_with_logs() {
let env = Env::default();
let contract_id = env.register(Contract, ());
let client = ContractClient::new(&env, &contract_id);
client.function_that_logs();
// Print all logs
env.logs().print();
// Get logs as vector
let logs = env.logs().all();
assert!(logs.contains(&"Expected log message".to_string()));
}
Address
Generate test addresses.
pub trait Address {
fn generate(env: &Env) -> crate::Address;
}
Example:
use soroban_sdk::{Env, Address, testutils::Address as _};
#[test]
fn test_with_addresses() {
let env = Env::default();
let user1 = Address::generate(&env);
let user2 = Address::generate(&env);
let user3 = Address::generate(&env);
// Use addresses in tests
}
MuxedAddress
Generate test muxed addresses.
pub trait MuxedAddress {
fn generate(env: &Env) -> crate::MuxedAddress;
fn new<T: Into<crate::MuxedAddress>>(address: T, id: u64) -> crate::MuxedAddress;
}
Example:
use soroban_sdk::{Env, MuxedAddress, testutils::MuxedAddress as _};
#[test]
fn test_muxed_addresses() {
let env = Env::default();
// Generate random muxed address
let muxed = MuxedAddress::generate(&env);
// Create with specific ID
let muxed2 = MuxedAddress::new(muxed.clone(), 42);
}
BytesN
Generate random bytes for testing.
pub trait BytesN<const N: usize> {
fn random(env: &Env) -> crate::BytesN<N>;
}
Example:
use soroban_sdk::{Env, BytesN, testutils::BytesN as _};
#[test]
fn test_with_random_bytes() {
let env = Env::default();
let random_hash: BytesN<32> = BytesN::random(&env);
let random_signature: BytesN<64> = BytesN::random(&env);
}
Deployer
Test utilities for contract deployment.
pub trait Deployer {
fn get_contract_instance_ttl(&self, contract: &Address) -> u32;
fn get_contract_code_ttl(&self, contract: &Address) -> u32;
}
Example:
use soroban_sdk::{Env, testutils::Deployer};
#[test]
fn test_ttl() {
let env = Env::default();
let contract_id = env.register(Contract, ());
let instance_ttl = env.deployer().get_contract_instance_ttl(&contract_id);
let code_ttl = env.deployer().get_contract_code_ttl(&contract_id);
assert!(instance_ttl > 0);
}
Types
LedgerInfo
Complete ledger information for testing.
pub struct LedgerInfo {
pub protocol_version: u32,
pub sequence_number: u32,
pub timestamp: u64,
pub network_id: [u8; 32],
pub base_reserve: u32,
pub min_temp_entry_ttl: u32,
pub min_persistent_entry_ttl: u32,
pub max_entry_ttl: u32,
}
ContractEvents
Collection of contract events for testing.
pub struct ContractEvents {
// ...
}
Methods:
events() -> &[ContractEvent] - Get events in XDR form
filter_by_contract(&self, addr: &Address) -> Self - Filter by contract address
Snapshot
Complete environment snapshot for testing.
pub struct Snapshot {
pub generators: Generators,
pub auth: AuthSnapshot,
pub ledger: LedgerSnapshot,
pub events: EventsSnapshot,
}
Methods:
read(r: impl Read) -> Result<Snapshot, Error> - Read snapshot from reader
read_file(p: impl AsRef<Path>) -> Result<Snapshot, Error> - Read from file
write(&self, w: impl Write) -> Result<(), Error> - Write to writer
write_file(&self, p: impl AsRef<Path>) -> Result<(), Error> - Write to file
Budget
Budget tracking for resource consumption.
pub struct Budget(/* ... */);
Methods:
reset_default(&mut self) - Reset to default limits
reset_unlimited(&mut self) - Reset with no limits
reset_limits(&mut self, cpu: u64, mem: u64) - Reset with custom limits
cpu_instruction_cost(&self) -> u64 - Get CPU instruction cost
memory_bytes_cost(&self) -> u64 - Get memory cost
tracker(&self, cost_type: ContractCostType) -> CostTracker - Get cost tracker
print(&self) - Print budget information
Example:
use soroban_sdk::{Env, testutils::budget::Budget};
#[test]
fn test_budget() {
let env = Env::default();
env.cost_estimate().budget().reset_default();
let contract_id = env.register(Contract, ());
let client = ContractClient::new(&env, &contract_id);
// Run expensive operation
client.complex_function();
// Check costs
let cpu_cost = env.cost_estimate().budget().cpu_instruction_cost();
let mem_cost = env.cost_estimate().budget().memory_bytes_cost();
println!("CPU: {}, Memory: {}", cpu_cost, mem_cost);
}
Mock Authentication
MockAuth
Mock authentication for testing.
pub trait MockAuth<T> {
fn mock_auths(&self, f: impl FnOnce()) -> T;
fn mock_all_auths(&self, f: impl FnOnce()) -> T;
}
Example:
use soroban_sdk::{Env, Address, testutils::{Address as _, MockAuth}};
#[test]
fn test_with_mock_auth() {
let env = Env::default();
let contract_id = env.register(Contract, ());
let client = ContractClient::new(&env, &contract_id);
let user = Address::generate(&env);
// Mock authentication for user
env.mock_all_auths();
// This will succeed even without real signatures
client.requires_auth_function(&user, &100);
}
AuthorizedInvocation
Represents an authorized invocation for verification.
pub struct AuthorizedInvocation {
// ...
}
Stellar Asset Testing
StellarAssetContract
Test utilities for Stellar Asset Contracts.
pub struct StellarAssetContract {
// ...
}
Methods:
address(&self) -> Address - Get contract address
issuer(&self) -> StellarAssetIssuer - Get issuer
StellarAssetIssuer
Manage issuer state for testing.
pub struct StellarAssetIssuer {
// ...
}
Methods:
flags(&self) -> u32 - Get issuer flags
set_flag(&self, flag: IssuerFlags) - Set a flag
clear_flag(&self, flag: IssuerFlags) - Clear a flag
address(&self) -> Address - Get issuer address
Snapshot Testing
SnapshotSource
Provide custom ledger data sources for testing.
pub trait SnapshotSource {
fn get(
&self,
key: &Rc<LedgerKey>
) -> Result<Option<(Rc<LedgerEntry>, Option<u32>)>, HostError>;
}
Input for creating environment from snapshot source.
pub struct SnapshotSourceInput {
pub source: Rc<dyn SnapshotSource>,
pub ledger_info: Option<LedgerInfo>,
pub snapshot: Option<Rc<LedgerSnapshot>>,
}
Example:
use soroban_sdk::{Env, testutils::{SnapshotSource, SnapshotSourceInput}};
use std::rc::Rc;
struct CustomSource;
impl SnapshotSource for CustomSource {
fn get(
&self,
key: &Rc<LedgerKey>
) -> Result<Option<(Rc<LedgerEntry>, Option<u32>)>, HostError> {
Ok(None)
}
}
#[test]
fn test_with_custom_source() {
let input = SnapshotSourceInput {
source: Rc::new(CustomSource),
ledger_info: None,
snapshot: None,
};
let env = Env::from_ledger_snapshot(input);
// Use env for testing
}
EnvTestConfig
Configuration for test environments.
pub struct EnvTestConfig {
// ...
}
Use with Env::from_ledger_snapshot_with_config for advanced test setup.
Examples
Complete Test Example
use soroban_sdk::{
contract, contractimpl, Env, Address, Symbol,
testutils::{Address as _, Ledger, Events, MockAuth}
};
#[contract]
pub struct Counter;
#[contractimpl]
impl Counter {
pub fn increment(env: Env, user: Address) -> u32 {
user.require_auth();
let key = Symbol::new(&env, "count");
let mut count: u32 = env.storage().instance()
.get(&key)
.unwrap_or(0);
count += 1;
env.storage().instance().set(&key, &count);
env.events().publish((Symbol::new(&env, "increment"), user), count);
count
}
}
#[test]
fn test_increment() {
let env = Env::default();
// Set ledger state
env.ledger().set_timestamp(1234567890);
env.ledger().set_sequence_number(100);
// Register contract
let contract_id = env.register(Counter, ());
let client = CounterClient::new(&env, &contract_id);
// Generate test address
let user = Address::generate(&env);
// Mock authentication
env.mock_all_auths();
// Call contract
let result = client.increment(&user);
assert_eq!(result, 1);
// Verify events
let events = env.events().all();
assert_eq!(events.len(), 1);
// Check budget
let cpu_cost = env.cost_estimate().budget().cpu_instruction_cost();
println!("CPU cost: {}", cpu_cost);
}
Best Practices
- Use mock auth: Use
mock_all_auths() for testing without real signatures
- Set ledger state: Always set appropriate ledger state for time-dependent tests
- Verify events: Check that expected events are emitted
- Track costs: Monitor resource consumption with budget tracking
- Generate addresses: Use
Address::generate() for unique test addresses
- Use snapshots: Leverage snapshots for consistent test state
See Also
- Env - Environment type with test configuration
- Address - Address type with test utilities
- auth - For authentication and authorization