| name | evolve-testing |
| description | Overview of Evolve SDK testing infrastructure including TestApp, SimTestApp, MockEnv, and transaction generators. Use when writing tests, setting up test harnesses, using test infrastructure, or choosing between testing approaches. Use when this capability is needed. |
| metadata | {"author":"evstack"} |
Evolve Testing Infrastructure
The Evolve SDK provides a layered testing infrastructure for unit tests, integration tests, stress testing, and property-based testing.
Test Harnesses
TestApp - Simple Unit Testing
Use for fast, isolated tests without full simulation:
use testapp::TestApp;
#[test]
fn test_basic_flow() {
let mut app = TestApp::new();
let accounts = app.accounts();
app.system_exec_as(accounts.alice, |env| {
let token = TokenRef::from(accounts.atom);
token.transfer(accounts.bob, 100, env)
}).unwrap();
app.next_block();
app.go_to_height(10);
let asset = app.mint_atom(accounts.alice, 1000);
}
When to use: Unit tests, quick functional verification, no need for deterministic replay.
SimTestApp - Full Simulation
Use for integration tests with deterministic execution, tracing, and fault injection:
use testapp::SimTestApp;
use evolve_simulator::SimConfig;
#[test]
fn test_with_simulation() {
let mut app = SimTestApp::new();
let mut app = SimTestApp::with_config(SimConfig::default(), 42);
let accounts = app.accounts();
let block = TestBlock::new(1, vec![tx1, tx2]);
let result = app.apply_block(&block);
let results = app.run_blocks_with(10, |height, sim| {
vec![generate_tx(height, sim)]
});
app.simulator().time().block_height();
app.simulator_mut().rng().gen_range(0..100);
}
When to use: Integration tests, multi-block scenarios, deterministic reproduction, trace capture.
Transaction Generators
For simulation-based testing, use the registry pattern:
use testapp::{TxGeneratorRegistry, TokenTransferGenerator};
#[test]
fn test_with_generators() {
let mut app = SimTestApp::new();
let accounts = app.accounts();
let mut registry = TxGeneratorRegistry::new();
registry.register(10, TokenTransferGenerator::new(
accounts.atom,
vec![accounts.alice, accounts.bob],
vec![accounts.alice, accounts.bob],
1,
100,
100000,
));
let results = app.run_blocks_with_registry(100, &mut registry);
for result in &results {
assert!(result.tx_results.iter().all(|r| r.response.is_ok()));
}
}
Custom Generator
use testapp::TxGenerator;
use evolve_simulator::Simulator;
struct MyGenerator {
}
impl TxGenerator for MyGenerator {
fn generate_tx(&mut self, height: u64, sim: &mut Simulator) -> Option<TestTx> {
let amount = sim.rng().gen_range(1..=100);
Some(TestTx {
sender: self.sender,
recipient: self.target,
request: InvokeRequest::new(&MyMsg { amount }).ok()?,
gas_limit: 100000,
funds: vec![],
})
}
}
Trace Recording
Capture execution traces for debugging:
use evolve_debugger::{TraceBuilder, StateSnapshot};
#[test]
fn test_with_trace() {
let mut app = SimTestApp::new();
let (results, trace) = app.run_blocks_with_trace(10, |height, sim| {
vec![generate_tx(height, sim)]
});
let snapshot = StateSnapshot::from_data(
app.simulator().storage().snapshot().data,
app.simulator().time().block_height(),
app.simulator().time().now_ms(),
);
let mut builder = TraceBuilder::new(app.simulator().seed_info().seed, snapshot);
let result = app.apply_block_with_trace(&block, &mut builder);
let trace = builder.finish();
trace.save("test_trace.bin").();
}
Mock Environment
For testing modules in isolation without the STF:
use evolve_testing::MockEnv;
use evolve_core::AccountId;
#[test]
fn test_module_directly() {
let contract = AccountId::new(1);
let sender = AccountId::new(2);
let mut env = MockEnv::new(contract, sender);
let module = MyModule::default();
module.initialize(sender, &mut env).unwrap();
env = env.with_sender(AccountId::new(3));
let result = module.admin_only(&mut env);
assert!(result.is_err());
}
MockEnv with Failure Injection
use evolve_collections::mocks::MockEnvironment;
let mut env = MockEnvironment::default();
env = env.with_failure();
let result = module.some_operation(&mut env);
assert!(result.is_err());
Test Patterns
Setup Helper
fn setup_test_environment() -> (StorageMock, AccountStorageMock, CustomStf, GenesisAccounts) {
let bootstrap_stf = build_stf(SystemAccounts::placeholder(), PLACEHOLDER_ACCOUNT);
let mut codes = AccountStorageMock::default();
install_account_codes(&mut codes);
let mut state = StorageMock::default();
let (genesis_state, accounts) = do_genesis(&bootstrap_stf, &codes, &state).unwrap();
state.apply_changes(genesis_state.into_changes().unwrap()).unwrap();
let stf = build_stf(SystemAccounts::new(accounts.gas_service), accounts.scheduler);
(state, codes, stf, accounts)
}
Block Execution Test
#[test]
fn test_block_execution() {
let (mut storage, codes, stf, accounts) = setup_test_environment();
let tx = TestTx {
sender: accounts.alice,
recipient: accounts.atom,
request: InvokeRequest::new(&TransferMsg { to: accounts.bob, amount: 50 }).unwrap(),
gas_limit: 100000,
funds: vec![],
};
let block = TestBlock::new(1, vec![tx]);
let (result, new_state) = stf.apply_block(&storage, &codes, &block);
assert!(result.tx_results[0].response.is_ok());
storage.apply_changes(new_state.into_changes().unwrap()).unwrap();
}
State Verification
#[test]
fn test_state_changes() {
let mut app = TestApp::new();
let accounts = app.accounts();
let before = app.system_exec_as(accounts.alice, |env| {
TokenRef::from(accounts.atom).get_balance(accounts.alice, env)
}).unwrap();
app.system_exec_as(accounts.alice, |env| {
TokenRef::from(accounts.atom).transfer(accounts.bob, 100, env)
}).unwrap();
let after = app.system_exec_as(accounts.alice, |env| {
TokenRef::from(accounts.atom).get_balance(accounts.alice, env)
}).unwrap();
assert_eq!(before.unwrap() - after.unwrap(), 100);
}
Benchmarking
Use Criterion with iter_batched for proper setup/teardown:
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
fn bench_apply_block(c: &mut Criterion) {
let mut group = c.benchmark_group("block_execution");
for tx_count in [10, 100, 500] {
group.bench_function(format!("apply_block_{tx_count}_txs"), |b| {
b.iter_batched(
|| {
let mut app = SimTestApp::new();
let block = make_block(tx_count, &mut app);
(app, block)
},
|(mut app, block)| {
app.apply_block(&block)
},
BatchSize::SmallInput,
)
});
}
group.finish();
}
criterion_group!(benches, bench_apply_block);
criterion_main!(benches);
When to Use What
| Scenario | Tool |
|---|
| Unit test a single module | MockEnv |
| Test module interactions | TestApp |
| Multi-block scenarios | SimTestApp |
| Deterministic reproduction | SimTestApp with fixed seed |
| Stress testing with faults | SimTestApp with SimConfig::stress_test() |
| Debug failing test | apply_block_with_trace + debugger |
| Property-based testing | evolve_proptest + SimTestApp |
| Performance benchmarks | Criterion + SimTestApp |
Files
crates/app/testapp/src/testing.rs - TestApp harness
crates/app/testapp/src/sim_testing.rs - SimTestApp harness
crates/app/sdk/testing/src/server_mocks.rs - Storage/code mocks
crates/app/sdk/collections/src/mocks.rs - MockEnvironment
crates/testing/simulator/ - Deterministic simulator
crates/testing/debugger/ - Trace recording/replay
crates/testing/proptest/ - Property-based testing
crates/app/testapp/benches/ - Benchmark examples
Converted and distributed by TomeVault — claim your Tome and manage your conversions.