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!");
}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) {}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.
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) {}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