suitecase 0.0.2

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, 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)



Install

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

[dependencies]
suitecase = "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 targetcargo test --example quickstart.

use suitecase::{cargo_case_tests_with_hooks, suite_methods, Case, HookFns};

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

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

static MY_CASES: &[Case<Counter>] = suite_methods![Counter, s => test_inc];

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

cargo_case_tests_with_hooks!(Counter::default(), MY_CASES, MY_HOOKS, [test_inc]);

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.rscargo test --example hooks.

use suitecase::{cargo_case_tests_with_hooks, suite_methods, Case, HookFns};

#[derive(Default)]
struct State {
    log: Vec<&'static str>,
}

impl State {
    fn test_ok(&mut self) {
        self.log.push("case");
    }
}

fn setup(s: &mut State) {
    s.log.push("setup");
}

static MY_CASES: &[Case<State>] = suite_methods![State, s => test_ok];

static MY_HOOKS: HookFns<State> = HookFns {
    setup_suite: Some(setup),
    teardown_suite: None,
    before_each: None,
    after_each: None,
};

cargo_case_tests_with_hooks!(State::default(), MY_CASES, MY_HOOKS, [test_ok]);

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.rscargo test --example filter.

use suitecase::{cargo_case_tests_with_hooks, suite_methods, Case, HookFns};

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

impl Counter {
    fn test_a(&mut self) {
        self.n = 1;
    }
    fn test_b(&mut self) {
        self.n = 2;
    }
}

static MY_CASES: &[Case<Counter>] = suite_methods![Counter, s => test_a, test_b];

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

cargo_case_tests_with_hooks!(Counter::default(), MY_CASES, MY_HOOKS, [test_a, test_b]);

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.rscargo test --example per_case_tests.

use suitecase::{cargo_case_tests_with_hooks, suite_methods, Case, HookFns};

// static MY_CASES: &[Case<MySuite>] = suite_methods![MySuite, s => test_a, test_b];
// static MY_HOOKS: HookFns<MySuite> = HookFns { /* ... */ };

cargo_case_tests_with_hooks!(MySuite::default(), MY_CASES, MY_HOOKS, [test_a, test_b]);

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:

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

See tests/suite.rs and tests/sqlx_sqlite.rs for full implementations of these patterns.


More documentation

  • cargo doc --open — full API, execution model, and macro Declaration / Description / Example sections.
  • Crate root and suite module describe how run selects cases and orders hooks.