suitecase 0.0.3

The structured test toolkit. A sync Rust crate for named cases, optional suite and per-case hooks, and macros so each case appears in cargo test—without a custom harness or DSL.
Documentation

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. Build case lists with cases!; use test_suite! so each case appears as its own line in cargo test, with all those tests sharing one suite behind a Mutex.

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 runnerrun orchestrates hooks and case bodies; integrate async I/O with something like tokio::runtime::Handle::block_on in hooks or cases.
  • Hooks as optional fnsHookFns holds Option<fn(&mut S)> per lifecycle slot; use [None] to skip.
  • Macroscases! builds &'static [Case<S>]. test_suite! emits one #[test] per case name; each run uses RunConfig::filter and the same shared suite (see macro docs for Send and ordering).

Install

Add suitecase to your Cargo.toml (no required dependencies beyond std):

[dependencies]
suitecase = "0.1"

Usage

Quickstart

Put this in tests/suite.rs or #[cfg(test)] mod tests { ... }. The example is cargo test --example quickstartone test line that runs every case in order on one suite.

use suitecase::{cases, run, Case, HookFns, RunConfig};

#[derive(Default)]
struct Counter {
    n: i32,
}

impl Counter {
    fn test_inc(&mut self) {
        self.n += 1;
    }
    fn test_inc_verify(&mut self) {
        assert_eq!(self.n, 1);
    }
}

static MY_CASES: &[Case<Counter>] = cases![Counter, s =>
    test_inc => { s.test_inc(); },
    test_inc_verify => { s.test_inc_verify(); },
];

static MY_HOOKS: HookFns<Counter> = HookFns {
    setup_suite: None,
    teardown_suite: None,
    before_each: None,
    after_each: None,
};

#[test]
fn quickstart() {
    let mut suite = Counter::default();
    run(&mut suite, MY_CASES, RunConfig::all(), &MY_HOOKS);
}

Run: cargo test.

Hooks

Pass HookFns as the last argument to run. Each field is Option<fn(&mut S)> — [Some(...)] or [None] / [HookFns::default()].

One line per case in cargo test (test_suite!)

test_suite! expands to one #[test] per listed case. Each test locks a shared Mutex<S> (keyed by a static name you choose), then calls [run] with [RunConfig::filter] for that case. Later tests can observe mutations from earlier runs on the same suite value.

use suitecase::{cases, test_suite, Case, HookFns};

// MY_CASES, MY_HOOKS, Counter …

test_suite!(
    Counter,
    MY_SHARED_SUITE,
    Counter::default(),
    MY_CASES,
    MY_HOOKS,
    [test_inc, test_inc_verify]
);

Run: cargo test. See the macro’s rustdoc for ordering (parallel harness) vs a single run with RunConfig::all.


Examples

Example Run
examples/quickstart.rs cargo test --example quickstart
examples/sqlx_sqlite.rs cargo test --example sqlx_sqlite
Every example at once cargo test --examples
Integration tests (tests/suite.rs) cargo test

The sqlx example uses sqlx + tokio, embedded migrations in setup_suite, and cases! blocks that call helper functions, plus test_suite!.

[dependencies]
suitecase = "0.1"
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "migrate"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

More documentation

  • cargo doc --opencases! and test_suite! include declaration / description / examples.
  • Crate root and suite describe how run selects cases and orders hooks.