run_test — The Test Harness

run_test is the test equivalent of run_blocking. Use it in every #[test] that runs an effect.

Basic Usage

#![allow(unused)]
fn main() {
use id_effect::{run_test, succeed, Exit};

#[test]
fn simple_effect_succeeds() {
    let exit = run_test(succeed(42), ());
    assert_eq!(exit, Exit::Success(42));
}
}

run_test returns an Exit<A, E> rather than a Result<A, E>. This lets you assert on the exact exit reason — success, typed failure, defect, or cancellation.

Why Not run_blocking in Tests?

run_blocking is correct but missing test-specific guarantees:

Featurerun_blockingrun_test
Runs the effect
Detects fiber leaks
Deterministic scheduling
Reports leaked resources

Fiber leaks — effects that spawn children and don't join them — are silent in production but become test failures under run_test. This catches a class of resource leak bugs at unit-test time.

Asserting on Exit

#![allow(unused)]
fn main() {
#[test]
fn division_by_zero_fails() {
    let eff = divide(10, 0);
    let exit = run_test(eff, ());

    // Assert specific failure
    assert!(matches!(exit, Exit::Failure(Cause::Fail(DivError::DivisionByZero))));
}

#[test]
fn effect_that_panics_is_a_defect() {
    let eff = effect!(|_r: &mut ()| {
        panic!("oops");
    });
    let exit = run_test(eff, ());

    assert!(matches!(exit, Exit::Failure(Cause::Die(_))));
}
}

Exit::Success(a) — the effect succeeded with value a Exit::Failure(Cause::Fail(e)) — the effect failed with typed error e Exit::Failure(Cause::Die(s)) — the effect panicked or encountered a defect Exit::Failure(Cause::Interrupt) — the effect was cancelled

run_test with an Environment

When your effect needs capabilities, build a test environment with build_env or manual Env::insert:

#![allow(unused)]
fn main() {
struct Database;

mock_capability!(MockDb, Database, Arc<dyn Db>, "db/mock", || {
    Arc::new(FakeDatabase::new()) as Arc<dyn Db>
});

#[test]
fn create_user_inserts_into_db() {
    let env = build_env([provide!(MockDb)]).expect("env");
    let fake_db = env.get::<Cap<Database>>().clone();

    let eff = create_user(NewUser { name: "Alice".into(), age: 30 });
    let exit = run_test(eff, env);

    assert!(matches!(exit, Exit::Success(_)));
    assert_eq!(fake_db.users().len(), 1);
}
}

run_test(effect, env) is the full signature. Pass () as the second argument when the effect requires no environment.

Unwrapping success in tests

When you're confident an effect succeeds and just want the value, match on Exit:

#![allow(unused)]
fn main() {
#[test]
fn addition_works() {
    let exit = run_test(succeed(1 + 1), ());
    let Exit::Success(result) = exit else {
        panic!("expected success, got {exit:?}");
    };
    assert_eq!(result, 2);
}
}

Explicit matching keeps failure variants visible in the test — useful when a non-success Exit indicates a bug in the test setup.

Fiber Leak Detection

#![allow(unused)]
fn main() {
#[test]
fn this_test_will_fail_due_to_leak() {
    let eff = effect!(|_r: &mut ()| {
        // Spawns a fiber but never joins it
        run_fork(/* … */);
        ()
    });

    // run_test detects the leaked fiber and fails the test
    let exit = run_test(eff, ());
    // exit: Exit::Failure(Cause::Die("fiber leak detected: 1 fiber(s) not joined"))
}
}

Fix leaks by joining fibers or explicitly cancelling them before the effect completes.