This PR solves the issue identified at https://codeberg.org/JasterV/test-context/issues/2. Previously, when defining the function argument that implements the `TestContext` trait, only "Ident" patterns were accepted (That is, only names). So, the following was valid: ```rust #[test] fn my_test(context: MyContext) {} ``` But the following would throw an error: ```rust #[test] fn my_test(MyContext { n }: MyContext {} ``` With this PR, we are now able to accept any kind of "pattern" such as struct pattern matching, enum pattern matching... etc. We only really care about the "type", and not the "pattern", so this PR makes sure that the "pattern" part of the binding is left untouched. Tests have been added to ensure that destructuring a struct compiles Co-authored-by: JasterV <49537445+JasterV@users.noreply.github.com> Reviewed-on: https://codeberg.org/JasterV/test-context/pulls/4 |
||
|---|---|---|
| .woodpecker | ||
| test-context | ||
| test-context-macros | ||
| .gitignore | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| cliff.toml | ||
| deny.toml | ||
| LICENSE | ||
| Makefile.toml | ||
| README.md | ||
| README.tpl | ||
| release-plz.toml | ||
| rust-toolchain.toml | ||
| yamlfmt.yml | ||
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