This document explains how to run the FlowPay test suite, how the benchmark file works, and how to interpret snapshot outputs.
cd contract
cargo testTo see printed output while tests run:
cargo test -- --nocaptureTo run a single test by name:
cargo test test_cancelFlowPay tests use the Soroban SDK test utilities:
Env::default()creates an in-memory chain environment.env.mock_all_auths()bypasses auth checks for test convenience.env.register_stellar_asset_contract_v2()deploys a real test token.env.ledger().with_mut()advances time for interval and grace-period tests.
The benchmark suite lives in contract/src/bench.rs. It measures instruction cost for the core contract paths:
bench_subscribe()bench_charge()bench_pay_per_use()bench_batch_charge_10_users()bench_charge_vs_subscribe_ratio()
These are not functional tests. They measure CPU and memory cost so regressions can be caught when the contract grows.
Run the benchmark tests with nocapture so the printed results stay visible:
cd contract
cargo test bench -- --nocaptureThat runs the benchmark functions and prints the measured CPU instructions and memory bytes for each one.
Each benchmark prints a small summary like:
[bench_charge]
CPU Instructions : 3800000
Memory Bytes : 180000
Interpretation:
CPU Instructionsis the Soroban instruction count for the measured call.Memory Bytesis the measured memory cost for that call.- The benchmark file compares the result against threshold constants such as
MAX_CHARGE_INSTRUCTIONS.
If a benchmark crosses its threshold, treat it as a regression unless you intentionally changed the contract behavior.
Benchmark and test snapshots live under contract/test_snapshots/.
These files are JSON captures of expected output. They are used to make benchmark and test behavior easy to compare over time.
Common fields you will see:
cpuorcpu_instruction_cost: instruction count for the call.memoryormemory_bytes_cost: memory cost for the call.events: emitted events when the snapshot includes contract logs.resultorreturn: the returned value from the call.
Units:
- Amounts are in stroops.
- Time is in seconds or ledger timestamps, depending on the benchmark or test.
- Benchmark cost numbers are instruction counts and memory bytes, not token amounts.
- Add a new
#[test]function in contract/src/bench.rs. - Use the shared
bench_setup()helper so the new benchmark matches the others. - Reset the budget immediately before the call you want to measure.
- Print CPU and memory numbers with a stable label.
- Add a threshold constant near the top of the file.
- If the benchmark should have a snapshot, add or update the matching file in
contract/test_snapshots/.
If a snapshot changes, decide whether the difference is deliberate or a regression:
- Deliberate change: update the snapshot and note the reason in the commit or PR.
- Regression: fix the code path and rerun the benchmark until the snapshot matches expectations again.
Do not blindly accept snapshot churn. Cost increases should be justified, especially in the contract hot path.
The test suite covers:
- Core subscription flows
- Multi-token behavior
- Batch charging
- Subscription counts and merchant stats
- Spending limits
- Referral tracking
- Migration
- Metadata
- Charge history
- TTL extension
Add new tests to contract/src/test.rs. Prefer the shared setup() helper to avoid boilerplate.
#[test]
fn test_your_feature() {
let (env, contract_id, _token_addr, user, merchant) = setup();
let client = FlowPayClient::new(&env, &contract_id);
client.subscribe(&user, &merchant, &1_0000000, &86400, &_token_addr, &None, &None);
}Use #[should_panic(expected = "...")] when you need to assert a failure path.
env.ledger().with_mut(|l| {
l.timestamp += 86_401;
});Frontend tests run with Vitest:
cd frontend
npm run test