This guide explains how to run and extend Tikka’s unit, integration, and fuzz tests. Commands below work from a fresh clone after installing the prerequisites for each layer.
| Layer | Requirements |
|---|---|
| Contract unit / integration | Stable Rust, wasm32-unknown-unknown target (rustup target add wasm32-unknown-unknown) |
| Oracle Jest suite | Node.js 20+, packages in oracle/ (cd oracle && npm ci) |
| Fuzz targets | Nightly Rust, cargo-fuzz, Linux or WSL (rustup toolchain install nightly && cargo install cargo-fuzz) |
Setup troubleshooting (WASM target, Stellar CLI skew, Node version) is covered in FAQ.md.
# Contract crates (from repo root)
cargo test -p raffle-shared
cargo test -p raffle-factory
cargo test -p raffle-instance
cargo test --workspace
# Oracle (from oracle/)
cd oracle
npm ci
npm test
# Fuzz (Linux/WSL, nightly; from repo root)
cargo +nightly fuzz run fuzz_buy_ticket -- -max_total_time=60
cargo +nightly fuzz run fuzz_finalize_raffle -- -max_total_time=60
cargo +nightly fuzz run fuzz_winner_selection -- -max_total_time=60
# Cross-platform fuzz smoke tests (stable Rust, any OS)
cargo test -p raffle-fuzzCI runs cargo test --workspace and npm test in oracle/ on every pull request (see .github/workflows/ci.yml).
| Crate | Role | Where tests live |
|---|---|---|
raffle-shared |
Shared types and pure helpers (e.g. effective_limit) |
#[cfg(test)] modules in contracts/raffle-shared/src/lib.rs |
raffle-factory |
Factory contract | Tests co-located in contracts/raffle-factory/src/lib.rs / related modules |
raffle-instance |
Per-raffle instance contract | contracts/raffle-instance/src/test.rs |
Contract tests use the Soroban SDK test environment (Env::default(), env.mock_all_auths(), testutils). They are integration-style: they register contracts, mint test tokens, and exercise public entrypoints.
From the repository root:
cargo test -p raffle-shared
cargo test -p raffle-factory
cargo test -p raffle-instance
# Optional: filter by test name
cargo test -p raffle-instance test_oracle_fallback
# Full workspace (matches CI)
cargo test --workspaceBefore opening a PR, also run formatting and Clippy as CI does:
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warningsFollow the house style used in contracts/raffle-instance/src/test.rs:
- Module header — start with
#![cfg(test)],use super::*;, and Sorobantestutilsimports. - Test names — prefer descriptive
snake_casenames that state the behavior under test (test_oracle_fallback_with_ledger_delays,non_winner_cannot_claim). Prefix withtest_when it aids grepping; issue numbers in comments (// #449) are welcome for context. - Env bootstrap — every test typically starts with:
Advance time or sequence with
let env = Env::default(); env.mock_all_auths();
env.ledger().with_mut(|l| { ... })when testing deadlines or timeouts. - Shared setup helpers — extract repeated scaffolding instead of duplicating it:
setup_active_raffle(...)— funded raffle ready for ticket salessetup_external_drawing_raffle(...)— external-oracle drawing pathlifecycle_config(...)— builds aRaffleConfigwith sensible defaults- Small assert helpers such as
assert_drawing_lock_cleared(...)
- Config builders — construct
RaffleConfig { ... }explicitly for the scenario; use named constants fromraffle_shared(e.g.DEFAULT_CLAIM_LOCKUP_SECONDS) in assertions rather than magic numbers when those constants define the expected behavior. - Token setup — register a stellar asset with
env.register_stellar_asset_contract_v2(...), then mint viaStellarAssetClient. - Clients — register with
env.register(RaffleInstance, ())and call throughRaffleInstanceClient. Prefertry_*methods when asserting specificErrorvariants.
When adding tests to other crates, mirror the same patterns: focused helpers, clear names, and assertions against shared constants where applicable.
The off-chain oracle under oracle/ has a Jest suite (*.test.ts next to sources).
cd oracle
npm ci # clean install from package-lock.json
npm run build # TypeScript compile check
npm test # jest --passWithNoTestsWrite new tests as *.test.ts beside the module under test. Prefer small, deterministic unit tests for services (keys, VRF, submitter, listener). Integration-style checks that need live RPC or secrets should stay behind env vars documented in oracle/README.md and should not break the default npm test run.
Fuzz targets live in fuzz/fuzz_targets/ and exercise pure numeric / state-machine guards extracted from contract logic (no Soroban host). Details and corpus/crash reproduction are also documented in fuzz/README.md.
| Target | Focus |
|---|---|
fuzz_buy_ticket |
Sold-out cap, deadline, multi-ticket policy, sold counter |
fuzz_finalize_raffle |
Winner-index bounds for internal and external randomness paths |
fuzz_winner_selection |
Winner selection invariants |
cargo-fuzz requires nightly and typically Linux or WSL:
rustup toolchain install nightly
cargo install cargo-fuzz
# From repo root — short smoke run
cargo +nightly fuzz run fuzz_buy_ticket -- -max_total_time=60
# Longer soak (e.g. 30 minutes)
cargo +nightly fuzz run fuzz_finalize_raffle -- -max_total_time=1800
cargo +nightly fuzz run fuzz_winner_selection -- -max_total_time=1800Each fuzz target embeds deterministic smoke tests runnable on stable Rust:
cargo test -p raffle-fuzz- Crashes land in
fuzz/artifacts/<target-name>/crash-<hash>— reproduce with
cargo +nightly fuzz run <target-name> fuzz/artifacts/<target-name>/crash-<hash> - Interesting inputs accumulate in
fuzz/corpus/<target-name>/— commit corpus updates that lock in regressions you care about.
| Prefer… | When… |
|---|---|
| Unit / integration test | You know the inputs and expected outcomes (happy path, specific error code, boundary values like effective_limit(0) / u32::MAX). Regression for a fixed bug. API contract changes. |
| Fuzz target | Invariants must hold for arbitrary inputs (index always in bounds, counters never overflow policy, sold-out never oversells). Large combinatorial state spaces where hand-written cases miss edge combinations. |
Practical rule of thumb:
- Fix or feature first gets a focused
#[test](or Jest case) with named constants and clear asserts. - If the logic is a closed numeric/state machine with many interacting fields, add or extend a fuzz harness that asserts invariants, plus a few smoke cases in the same file so
cargo test -p raffle-fuzzstays green on Windows/macOS CI contributors.
Do not replace unit tests with fuzz-only coverage: CI always runs cargo test --workspace; long fuzz soaks are optional local/CI jobs.
-
cargo test -p <crate>(or--workspace) passes for crates you touched -
cargo fmt --allapplied; Clippy clean if you changed Rust - If you changed
oracle/,cd oracle && npm test(and format checks if configured) passes - New behavior has a unit/integration test, or a documented fuzz invariant when appropriate
- Docs updated when test commands or conventions change