Testing
Evolve provides multiple testing levels: unit tests with MockEnv, integration tests with TestApp, and simulation tests with SimTestApp.
Unit Testing with MockEnv
use evolve_core::{AccountId, Environment};
use evolve_testing::MockEnv;
#[test]
fn test_basic_operation() {
let contract_id = AccountId::new(1);
let sender_id = AccountId::new(100);
let mut env = MockEnv::new(contract_id, sender_id);
let module = MyModule::default();
module.initialize(42, &mut env).expect("init failed");
let result = module.increment(&mut env).expect("increment failed");
assert_eq!(result, 43);
}MockEnv Builder
let mut env = MockEnv::new(contract_id, sender_id)
.with_sender(new_sender)
.with_funds(vec![fungible_asset])
.with_account_balance(account, token, amount);Testing Authorization
#[test]
fn test_unauthorized_access() {
let contract_id = AccountId::new(1);
let owner_id = AccountId::new(100);
let attacker_id = AccountId::new(999);
let mut env = MockEnv::new(contract_id, owner_id);
let module = MyModule::default();
module.initialize(&mut env).unwrap();
// Try as attacker
env = env.with_sender(attacker_id);
let result = module.admin_only_function(&mut env);
assert!(matches!(result, Err(e) if e == ERR_UNAUTHORIZED));
}Testing Payable Functions
#[test]
fn test_deposit() {
let mut env = MockEnv::new(contract_id, depositor_id)
.with_funds(vec![FungibleAsset {
asset_id: token_id,
amount: 1000,
}]);
let module = MyModule::default();
module.deposit(&mut env).expect("deposit failed");
assert_eq!(env.funds().len(), 0); // Funds consumed
}Testing Error Conditions
#[test]
fn test_insufficient_balance() {
let mut env = setup_env();
let module = MyModule::default();
module.initialize(vec![(sender, 100)], &mut env).unwrap();
let result = module.transfer(recipient, 1000, &mut env);
assert!(matches!(result, Err(e) if e == ERR_NOT_ENOUGH_BALANCE));
}Property-Based Testing
Use proptest for exhaustive testing:
use proptest::prelude::*;
proptest! {
#[test]
fn test_transfer_preserves_total(
balance1 in 0u128..1_000_000,
balance2 in 0u128..1_000_000,
transfer_amount in 0u128..1_000_000,
) {
let mut env = setup_env();
let module = MyModule::default();
module.initialize(
vec![(account1, balance1), (account2, balance2)],
&mut env
).unwrap();
let total_before = balance1 + balance2;
// Attempt transfer (may fail)
let _ = module.transfer(account1, account2, transfer_amount, &mut env);
// Total preserved regardless
let b1 = module.get_balance(account1, &env).unwrap().unwrap_or(0);
let b2 = module.get_balance(account2, &env).unwrap().unwrap_or(0);
prop_assert_eq!(b1 + b2, total_before);
}
}Integration Testing with TestApp
use evolve_testapp::TestApp;
#[test]
fn test_full_block_execution() {
let app = TestApp::new();
let result = app.execute_block(vec![
transaction1,
transaction2,
]);
assert!(result.is_ok());
assert_eq!(result.tx_results.len(), 2);
}Simulation Testing
For deterministic testing with fault injection:
use evolve_testing::SimTestApp;
#[test]
fn test_with_simulation() {
let sim = SimTestApp::builder()
.with_seed(42) // Deterministic
.with_fault_injection(true)
.build();
// Run simulation
sim.run_for_blocks(100);
// Check invariants
assert!(sim.check_invariants());
}Testing Determinism
Run the same test multiple times and verify identical results:
#[test]
fn test_determinism() {
for _ in 0..100 {
let result = execute_module_logic();
assert_eq!(result, EXPECTED_VALUE);
}
}Testing Overflow Protection
#[test]
fn test_overflow_protection() {
let mut env = setup_env();
let module = MyModule::default();
// Set balance near u128::MAX
module.set_balance(u128::MAX - 10, &mut env).unwrap();
// Adding 100 should overflow
let result = module.add_balance(100, &mut env);
assert!(matches!(result, Err(e) if e == ERR_OVERFLOW));
}Best Practices
- Test both success and failure paths
- Use property-based testing for arithmetic
- Test authorization for all privileged functions
- Test edge cases (zero, max values, empty collections)
- Test determinism by running tests multiple times
- Test overflow protection for all arithmetic operations