Skip to content

JasterV/test-context

Repository files navigation

crates.io Documentation License Github

test-context

A library for providing custom setup/teardown for Rust tests without needing a test harness.

use test_context::{test_context, TestContext};

struct MyContext {
    value: String
}

impl TestContext for MyContext {
    fn setup() -> MyContext {
        MyContext {  value: "Hello, World!".to_string() }
    }

    fn teardown(self) {
        // Perform any teardown you wish.
    }
}

#[test_context(MyContext)]
#[test]
fn test_works(ctx: &mut MyContext) {
    assert_eq!(ctx.value, "Hello, World!");
}

struct MyGenericContext<T> {
    value: T
}

impl TestContext for MyGenericContext<u32> {
    fn setup() -> MyGenericContext<u32> {
        MyGenericContext { value: 1 }
    }
}

#[test_context(MyGenericContext<u32>)]
#[test]
fn test_generic_type(ctx: &mut MyGenericContext<u32>) {
    assert_eq!(ctx.value, 1);
}

Alternatively, you can use async functions in your test context by using the AsyncTestContext.

use test_context::{test_context, AsyncTestContext};

struct MyAsyncContext {
    value: String
}

impl AsyncTestContext for MyAsyncContext {
    async fn setup() -> MyAsyncContext {
        MyAsyncContext { value: "Hello, World!".to_string() }
    }

    async fn teardown(self) {
        // Perform any teardown you wish.
    }
}

#[test_context(MyAsyncContext)]
fn test_works(ctx: &mut MyAsyncContext) {
    assert_eq!(ctx.value, "Hello, World!");
}

The AsyncTestContext works well with async test wrappers like actix_rt::test or tokio::test.

#[test_context(MyAsyncContext)]
#[tokio::test]
async fn test_works(ctx: &mut MyAsyncContext) {
    assert_eq!(ctx.value, "Hello, World!");
}

Attribute order

Place #[test_context(...)] before other test attributes like #[tokio::test] or #[test].

Why: Attributes expand in source order. #[test_context] generates a wrapper and reattaches the remaining attributes to it. It must run first so the test attribute applies to the wrapper that runs setup/teardown.

Valid:

#[test_context(MyAsyncContext)]
#[tokio::test]
async fn my_test(ctx: &mut MyAsyncContext) {}

Invalid:

#[tokio::test]
#[test_context(MyAsyncContext)]
async fn my_test(ctx: &mut MyAsyncContext) {}

Using AsyncTestContext in sync tests that require Tokio

By default, when you use an AsyncTestContext in a synchronous test (no #[tokio::test]), this crate runs setup/teardown using the futures executor. If your context calls Tokio-only APIs (e.g., tokio::time::sleep, timers, or Tokio sockets) during setup/teardown, enable the optional tokio-runtime feature so those steps run inside a Tokio runtime:

[dependencies]
test-context = { version = "0.5", features = ["tokio-runtime"] }

With this feature, the crate tries to reuse an existing runtime; if none is present, it creates an ephemeral current-thread Tokio runtime around setup and teardown for sync tests. Async tests annotated with #[tokio::test] continue to work as usual without the feature.

Skipping the teardown execution

Also, if you don't care about the teardown execution for a specific test, you can use the skip_teardown keyword on the macro like this:

 use test_context::{test_context, TestContext};

 struct MyContext {}

 impl TestContext for MyContext {
     fn setup() -> MyContext {
         MyContext {}
     }
 }

#[test_context(MyContext, skip_teardown)]
#[test]
fn test_without_teardown(ctx: &MyContext) {}

Taking ownership of the context vs taking a reference

If the teardown is ON (default behavior), you can only take a reference to the context, either mutable or immutable, as follows:

#[test_context(MyContext)]
#[test]
fn test_with_teardown_using_immutable_ref(ctx: &MyContext) {}   

#[test_context(MyContext)]
#[test]
fn test_with_teardown_using_mutable_ref(ctx: &mut MyContext) {}   

❌The following is invalid:

#[test_context(MyContext)]
#[test]
fn test_with_teardown_taking_ownership(ctx: MyContext) {}   

If the teardown is skipped (as specified in the section above), you can take an immutable ref, mutable ref or full ownership of the context:

#[test_context(MyContext, skip_teardown)]
#[test]
fn test_without_teardown(ctx: MyContext) {
  // Perform any operations that require full ownership of your context
}

#[test_context(MyContext, skip_teardown)]
#[test]
fn test_without_teardown_taking_a_ref(ctx: &MyContext) {}

#[test_context(MyContext, skip_teardown)]
#[test]
fn test_without_teardown_taking_a_mut_ref(ctx: &mut MyContext) {}

⚠️ Ensure that the context type specified in the macro matches the test function argument type exactly

The error occurs when a context type with an absolute path is mixed with an it's alias.

For example:

mod database {
    use test_context::TestContext;

    pub struct Connection;

    impl TestContext for Connection {
    	fn setup() -> Self { Connection }
    	fn teardown(self) {...}
	}
}

✅The following code will work:

use database::Connection as DbConn;

#[test_context(DbConn)]
#[test]
fn test1(ctx: &mut DbConn) {
	//some test logic
}

// or

use database::Connection

#[test_context(database::Connection)]
#[test]
fn test1(ctx: &mut database::Connection) {
	//some test logic
}

❌The following code will not work:

use database::Connection as DbConn;

#[test_context(database::Connection)]
#[test]
fn test1(ctx: &mut DbConn) {
	//some test logic
}

// or

use database::Connection as DbConn;

#[test_context(DbConn)]
#[test]
fn test1(ctx: &mut database::Connection) {
	//some test logic
}

Type mismatches will cause context parsing to fail during either static analysis or compilation.

License: MIT

About

A Rust library to allow for easy setup/teardown implementations for tests

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 10

Languages