Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

effect_test — The Test Harness Boundary

Effectful tests should compose effects in the test body and let the harness execute them. This keeps lazy execution semantics intact and keeps direct Effect::run(&mut ...) calls inside runtime/test harness internals.

Preferred Usage

use effectful::{Effect, effect_test};

#[effect_test]
fn simple_effect_succeeds() -> Effect<(), &'static str, ()> {
    Effect::new(|_| Ok(()))
}

#[effect_test] creates an async Tokio test and runs the returned effect. Ok(_) passes. Err(E) panics with Debug output, so the error type must implement Debug.

The macro uses effectful’s internal current-thread Tokio test re-export; downstream crates do not need to call Effect::run(&mut ...) in the test body.

Provided ServiceContext

use effectful::{Effect, MissingService, Service, ServiceContext, effect_test};

#[derive(Clone, Service)]
struct Config {
    port: u16,
}

fn test_env() -> ServiceContext {
    Config { port: 8080 }.to_context()
}

#[effect_test(env = "test_env")]
fn reads_config() -> Effect<(), MissingService, ServiceContext> {
    Effect::<Config, MissingService, ServiceContext>::service::<Config>()
        .map(|config| assert_eq!(config.port, 8080))
}

The fixture runs once per test and returns the environment consumed by the effect.

Layer-Based Setup

use effectful::{Effect, Layer, MissingService, Service, ServiceContext, effect_test};

#[derive(Clone, Service)]
struct Config {
    port: u16,
}

fn test_layer() -> Layer<Config, MissingService, ()> {
    Layer::succeed(Config { port: 8080 })
}

#[effect_test(layer = "test_layer")]
fn reads_layer_config() -> Effect<(), MissingService, ServiceContext> {
    Effect::<Config, MissingService, ServiceContext>::service::<Config>()
        .map(|config| assert_eq!(config.port, 8080))
}

This is the Rust equivalent of Effect’s it.layer(...) boundary: the test body remains an effect, and the adapter builds/provides the services.

Helper API

Use helper functions when an attribute macro is not appropriate.

use effectful::testing::expect_effect_test;

#[tokio::test]
async fn create_user_inserts_into_db() {
    expect_effect_test(create_user(NewUser { name: "Alice".into(), age: 30 })).await;
}

Available helpers:

FunctionNotes
expect_effect_test(effect).awaitRun with R: Default, panic on Err(E: Debug)
expect_effect_test_with_env(effect, env).awaitRun with explicit environment, panic on failure
expect_effect_test_with_layer(effect, layer).awaitBuild a layer for ServiceContext, panic on failure
run_effect_test(effect).awaitRun with R: Default, return Result<A, E>
run_effect_test_with_env(effect, env).awaitRun with explicit environment, return Result<A, E>
TestRuntime::with_env(fixture)Reusable adapter for explicit fixture functions

Asserting on Exit

run_test remains available when you need the older synchronous Exit<A, E> shape.

use effectful::{Exit, run_test};

#[test]
fn division_by_zero_fails() {
    let exit = run_test(divide(10, 0), ());
    assert!(matches!(exit, Exit::Failure(Cause::Fail(DivError::DivisionByZero))));
}

Pass () for effects with no environment. run_test(effect, env) resets the test leak counters, runs the effect with run_blocking, then checks the leak counters.

Common Exit shapes:

ExitMeaning
Exit::Success(a)Effect succeeded
Exit::Failure(Cause::Fail(e))Typed failure
Exit::Failure(Cause::Die(message))Defect message
Exit::Failure(Cause::Interrupt(id))Fiber interrupt

run_test_with_clock

use std::time::Instant;
use effectful::{TestClock, run_test_with_clock};

let clock = TestClock::new(Instant::now());
let exit = run_test_with_clock(effect, env, clock);

run_test_with_clock currently delegates to run_test after accepting the explicit clock argument. Use explicit-clock scheduling helpers (retry_with_clock, repeat_with_clock) when the effect itself must use that clock.

Leak Assertions

The testing module exposes assertion effects and test hooks:

use effectful::{assert_no_leaked_fibers, assert_no_unclosed_scopes};

run_blocking(assert_no_leaked_fibers(), ())?;
run_blocking(assert_no_unclosed_scopes(), ())?;

The effect-test helpers and run_test call both assertions after the effect run. If a hook recorded a leak, the assertion panics.