Suitcase
The structured test toolkit. A lightweight sync Rust library for named cases, optional setup / teardown at suite scope and before_each / after_each around each case, plus macros so every case can show up as its own line in cargo test—without a custom harness or DSL. Filter to a single case when you want isolation; hooks still run in the right order.
Heavy development: The API is still evolving. Expect breaking changes between releases until a stable 1.0; pin an exact version (or git revision) in Cargo.toml if you need upgrades to be predictable.
Install · Usage · Examples · Docs (after publish; cargo doc --open locally)
- Sync runner —
runorchestrates hooks and case bodies; integrate async I/O with something liketokio::runtime::Handle::block_onin hooks or cases. - Hooks as optional fns —
HookFnsholdsOption<fn(&mut S)>per lifecycle slot; use [None] to skip. - Named cases — build
&'static [Case<S>]withsuite_methods!,cases!, orcases_fn!. - One line per case in
cargo test—cargo_case_tests!/cargo_case_tests_with_hooks!emit a#[test]per case name (each run usesRunConfig::filter).
Install
Add suitecase to your Cargo.toml (no required dependencies beyond std):
[]
= "0.1"
Usage
Quickstart
Put this in tests/suite.rs (integration test) or inside #[cfg(test)] mod tests { ... } in your crate. The same flow is an example target — cargo test --example quickstart.
use ;
static MY_CASES: & = suite_methods!;
static MY_HOOKS: = HookFns ;
cargo_case_tests_with_hooks!;
Run: cargo test — one #[test] per name in the list ([test_inc] here).
Hooks
Pass HookFns as the last argument to run. Each field is Option<fn(&mut S)> — wrap your function in [Some(...)] or use [None] / [HookFns::default()] to skip.
Runnable: examples/hooks.rs — cargo test --example hooks.
use ;
static MY_CASES: & = suite_methods!;
static MY_HOOKS: = HookFns ;
cargo_case_tests_with_hooks!;
Run: cargo test
Run a single case (filter)
Each generated test calls run with RunConfig::filter for that case name — same effect as listing several cases in cargo_case_tests_with_hooks!.
Runnable: examples/filter.rs — cargo test --example filter.
use ;
static MY_CASES: & = suite_methods!;
static MY_HOOKS: = HookFns ;
cargo_case_tests_with_hooks!;
Run: cargo test
Show each case in cargo test
Rust lists one line per #[test]. Use cargo_case_tests! (default hooks) or cargo_case_tests_with_hooks! at the root of an integration test file (tests/*.rs) or under examples/ — all cookbook examples in this repo use cargo_case_tests_with_hooks!(suite, MY_CASES, MY_HOOKS, […]).
Runnable: examples/per_case_tests.rs — cargo test --example per_case_tests.
use ;
// static MY_CASES: &[Case<MySuite>] = suite_methods![MySuite, s => test_a, test_b];
// static MY_HOOKS: HookFns<MySuite> = HookFns { /* ... */ };
cargo_case_tests_with_hooks!;
In tests/*.rs, run cargo test — you should see one test per listed case name.
Each case body should be correct when it is the only case selected (fresh suite state unless you seed in the body).
Examples
| Example | Run |
|---|---|
examples/quickstart.rs |
cargo test --example quickstart |
examples/hooks.rs |
cargo test --example hooks |
examples/filter.rs |
cargo test --example filter |
examples/per_case_tests.rs |
cargo test --example per_case_tests |
examples/sqlx_sqlite.rs |
cargo test --example sqlx_sqlite |
| Every example at once | cargo test --examples |
Integration tests in this repo (tests/) |
cargo test |
The sqlx example uses sqlx + tokio, applies embedded migrations in setup_suite, and runs several free-function cases with cases_fn! and HookFns. Enable the same stack in your crate if you copy the pattern:
[]
= "0.1"
= { = "0.8", = ["runtime-tokio", "sqlite", "migrate"] }
= { = "1", = ["macros", "rt-multi-thread"] }
See tests/suite.rs and tests/sqlx_sqlite.rs for full implementations of these patterns.