Skip to main content

context

Macro context 

Source
context!() { /* proc-macro */ }
Expand description

Declares a test context with multiple services.

Supports optional inline configuration for each service using the syntax: service_name: ServiceType = config_expr

§Examples

use admixture::context;
use testcontainers_modules::postgres::Postgres;

context! {
    MyTestContext {
        primary_db: SqlxPostgresServiceSetup = Postgres::default().with_tag("15"),
        replica_db: SqlxPostgresServiceSetup = Postgres::default().with_tag("14"),
        redis: RedisServiceSetup = Redis::default(),
    }
}

§Without inline configuration (uses Config::default())

use admixture::context;

context! {
    MyTestContext {
        postgres: SqlxPostgresServiceSetup,
        redis: RedisServiceSetup,
    }
}
// Requires that each service's Config type implements Default

§Lifecycle Hooks

Contexts can define optional lifecycle hooks that run at specific points in the test lifecycle. Hooks are defined in a separate hooks { ... } block and can be placed in any order relative to service definitions.

use admixture::context;
use std::error::Error;

context! {
    TestContext {
        postgres: PostgresServiceSetup,
    },
    hooks {
        before_all = setup_test_data,
        after_all = cleanup_test_data,
        before_each = reset_database,
        after_each = verify_invariants,
    }
}

async fn setup_test_data(ctx: &TestContextRunning) -> Result<(), Box<dyn Error + Send>> {
    // Runs once after context starts, before any tests
    let client = ctx.postgres().client().await?;
    client.execute("INSERT INTO users (name) VALUES ('test')", &[]).await?;
    Ok(())
}

async fn reset_database(ctx: &TestContextRunning) -> Result<(), Box<dyn Error + Send>> {
    // Runs before each test
    ctx.postgres().client().await?.execute("TRUNCATE users", &[]).await?;
    Ok(())
}

async fn verify_invariants(ctx: &TestContextRunning) -> Result<(), Box<dyn Error + Send>> {
    // Runs after each test (even if test failed)
    let count: i64 = ctx.postgres().client().await?
        .query_one("SELECT COUNT(*) FROM users WHERE invalid = true", &[])
        .await?
        .get(0);
    if count > 0 {
        return Err("Found invalid users after test".into());
    }
    Ok(())
}

async fn cleanup_test_data(ctx: &TestContextRunning) -> Result<(), Box<dyn Error + Send>> {
    // Runs once after all tests, before context stops (best-effort)
    ctx.postgres().client().await?.execute("DROP TABLE IF EXISTS temp_data", &[]).await?;
    Ok(())
}

§Hook Execution Order

  1. Context starts
  2. before_all - If this fails, all tests in the context group fail
  3. For each test:
    • before_each - If this fails, that specific test fails
    • Test runs
    • after_each - If this fails, that test fails (always runs, even if test failed)
  4. after_all - Failure is logged but doesn’t fail tests (best-effort cleanup)
  5. Context stops

§Hook Failure Behavior

  • before_all failure → all tests in context group fail
  • before_each failure → that specific test fails
  • after_each failure → that specific test fails
  • after_all failure → logged as warning, doesn’t fail tests

§Hook Function Signature

All hook functions must have this signature:

async fn hook_name(ctx: &ContextNameRunning) -> Result<(), Box<dyn Error + Send>>

§Flexible Ordering

Both services and hooks can appear in any order:

context! {
    MyContext {
        hooks {
            before_each = reset_state,
        },
        postgres: PostgresServiceSetup,
        hooks {  // Can even split hooks if needed (though not recommended)
            after_each = verify_state,
        },
        redis: RedisServiceSetup,
    }
}

This generates:

  • A MyTestContextConfig struct with service config fields
  • A MyTestContextSetup struct with service setup fields
  • A MyTestContextRunning struct with running service fields
  • Implementations of ContextSetup and ContextRunning traits
  • A type alias MyTestContext = TestContext<MyTestContextRunning>
  • A constructor method MyTestContext::new(setup)
  • A static MYTESTCONTEXT_HOOKS containing hook function pointers