diff --git a/Cargo.toml b/Cargo.toml index 8fba3d2..c4b56b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ spin = { version = "0.9.8", optional = true } async-trait = "0.1" critical-section = { version = "1.1.2", features = ["std"] } tokio = { version = "1", features = ["full"] } +rustversion = "1" [lib] # do not run doctest by default with `cargo hack`. They are tested with a separate `cargo test --doc` run. diff --git a/README.md b/README.md index dd69067..5d994e7 100644 --- a/README.md +++ b/README.md @@ -320,8 +320,8 @@ These mock APIs can be found in [mock]. * Methods returning data borrowed from non-self arguments (these have to be converted to static reference, e.g. via [`Box::leak`](Box::leak)). * Methods returning a type containing lifetime parameters. For a mocked return they will have to be `'static`. * Generic methods using either explicit generic params or argument-position `impl Trait`. -* Async methods when the trait is annotated with `#[async_trait]`. -* Methods that return a future that is an associated type. Requires nightly. +* Methods that are `async` or return `impl Future`. +* `async_trait`-annotated traits. #### What kinds of traits or methods cannot be mocked? * Static methods, i.e. no `self` receiver. Static methods with a _default body_ are accepted though, but not mockable. diff --git a/src/lib.rs b/src/lib.rs index 9f9e6ee..47f6fe8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -340,8 +340,8 @@ //! * Methods returning data borrowed from non-self arguments (these have to be converted to static reference, e.g. via [`Box::leak`](Box::leak)). //! * Methods returning a type containing lifetime parameters. For a mocked return they will have to be `'static`. //! * Generic methods using either explicit generic params or argument-position `impl Trait`. -//! * Async methods when the trait is annotated with `#[async_trait]`. -//! * Methods that return a future that is an associated type. Requires nightly. +//! * Methods that are `async` or return `impl Future`. +//! * `async_trait`-annotated traits. //! //! #### What kinds of traits or methods cannot be mocked? //! * Static methods, i.e. no `self` receiver. Static methods with a _default body_ are accepted though, but not mockable. diff --git a/tests/it/async_fn.rs b/tests/it/async_fn.rs new file mode 100644 index 0000000..a53d5ea --- /dev/null +++ b/tests/it/async_fn.rs @@ -0,0 +1,50 @@ +#[rustversion::since(1.75)] +mod r#async { + use unimock::*; + + #[unimock(api = TraitMock)] + trait Trait { + async fn a(&self, arg: i32) -> i32; + async fn b(&self) -> &i32; + async fn c(&self) -> Option<&i32>; + } + + #[tokio::test] + async fn test_it() { + let deps = Unimock::new(( + TraitMock::a.next_call(matching!(_)).returns(42), + TraitMock::b.next_call(matching!()).returns(42), + TraitMock::c.next_call(matching!()).returns(Some(42)), + )); + + assert_eq!(42, deps.a(5).await); + assert_eq!(&42, deps.b().await); + assert_eq!(Some(&42), deps.c().await); + } +} + +#[rustversion::since(1.75)] +mod rpit_future { + use std::future::Future; + + use unimock::*; + + #[unimock(api = RpitFutureMock)] + trait RpitFuture { + fn m1(&self) -> impl Future; + fn m2(&self, arg: i32) -> impl Send + Future; + fn m3(&self) -> impl Future + Send; + fn m4(&self) -> impl core::future::Future + Send; + } + + #[tokio::test] + async fn rpit() { + let u = Unimock::new(( + RpitFutureMock::m1.next_call(matching!()).returns(1337), + RpitFutureMock::m2.next_call(matching!(42)).returns(1338), + )); + + assert_eq!(u.m1().await, 1337); + assert_eq!(u.m2(42).await, 1338); + } +} diff --git a/tests/it/basic.rs b/tests/it/basic.rs index 8883784..8f1dcfb 100644 --- a/tests/it/basic.rs +++ b/tests/it/basic.rs @@ -706,10 +706,44 @@ fn borrow_static_should_work_with_returns_static() { ); } -#[cfg(feature = "std")] +#[rustversion::since(1.75)] mod async_argument_borrowing { use super::*; + #[unimock(api=BorrowParamMock)] + trait BorrowParam { + async fn borrow_param<'a>(&self, arg: &'a str) -> &'a str; + } + + #[cfg(feature = "std")] + #[tokio::test] + async fn test_argument_borrowing() { + let unimock = Unimock::new( + BorrowParamMock::borrow_param + .each_call(matching!(_)) + .returns("foobar"), + ); + + assert_eq!("foobar", unimock.borrow_param("input").await); + } + + #[cfg(feature = "std")] + #[tokio::test] + async fn test_argument_borrowing_works() { + let unimock = Unimock::new( + BorrowParamMock::borrow_param + .each_call(matching!(_)) + .returns("foobar"), + ); + + unimock.borrow_param("input").await; + } +} + +#[cfg(feature = "std")] +mod async_trait_argument_borrowing { + use super::*; + #[unimock(api=BorrowParamMock)] #[::async_trait::async_trait] trait BorrowParam { @@ -755,10 +789,18 @@ mod lifetime_constrained_output_type { fn borrow_sync_explicit2<'a, 'b>(&'a self, arg: &'b str) -> Borrowing2<'a, 'b>; } + #[rustversion::since(1.75)] + #[unimock] + trait BorrowAsync { + async fn borrow_async_elided(&self) -> Borrowing1<'_>; + async fn borrow_async_explicit<'a>(&'a self) -> Borrowing1<'a>; + async fn borrow_async_explicit2<'a, 'b>(&'a self, arg: &'b str) -> Borrowing2<'a, 'b>; + } + #[cfg(feature = "std")] #[unimock] #[::async_trait::async_trait] - trait BorrowAsync { + trait BorrowAsyncTrait { async fn borrow_async_elided(&self) -> Borrowing1<'_>; async fn borrow_async_explicit<'a>(&'a self) -> Borrowing1<'a>; async fn borrow_async_explicit2<'a, 'b>(&'a self, arg: &'b str) -> Borrowing2<'a, 'b>; diff --git a/tests/it/default_impl.rs b/tests/it/default_impl.rs index 806561c..1bf484d 100644 --- a/tests/it/default_impl.rs +++ b/tests/it/default_impl.rs @@ -168,6 +168,35 @@ mod default_impl_arc { } } +#[rustversion::since(1.75)] +mod default_impl_async { + use unimock::*; + + type TestResult = Result; + + // A point of these tests is that the method names + // in both traits are the same. + // This tests that the delegation is done with fully-qualified syntax. + + #[unimock(api = AsyncTraitDefault1Mock)] + trait AsyncTraitDefault1 { + async fn normal_method(&self) -> TestResult; + + async fn default_method(&self) -> TestResult { + Ok(42) + } + } + + #[unimock(api = AsyncTraitDefault2Mock)] + trait AsyncTraitDefault2 { + async fn normal_method(&self) -> TestResult; + + async fn default_method(&self) -> TestResult { + Ok(42) + } + } +} + #[cfg(feature = "std")] mod default_impl_async_trait { use unimock::*; diff --git a/tests/it/generic.rs b/tests/it/generic.rs index ed40d05..14d8e19 100644 --- a/tests/it/generic.rs +++ b/tests/it/generic.rs @@ -113,10 +113,16 @@ mod combined { } } -#[cfg(feature = "std")] mod async_generic { use super::*; + #[rustversion::since(1.75)] + #[unimock] + trait AsyncGenericBounds { + async fn generic_bounds(&self, param: I) -> O; + } + + #[cfg(feature = "std")] #[unimock] #[async_trait::async_trait] trait AsyncTraitGenericBounds { @@ -273,8 +279,14 @@ mod generic_combo { U: 'static; } - #[cfg(feature = "std")] + #[rustversion::since(1.75)] #[unimock(api=MockAsyncCombo)] + trait AsyncGenerics { + async fn ret(&self, u: U, a: impl Any + Send + 'static) -> T; + } + + #[cfg(feature = "std")] + #[unimock(api=MockAsyncTraitCombo)] #[async_trait::async_trait] trait AsyncTraitGenerics { async fn ret(&self, u: U, a: impl Any + Send + 'static) -> T; diff --git a/tests/it/main.rs b/tests/it/main.rs index 561d86f..69c57b6 100644 --- a/tests/it/main.rs +++ b/tests/it/main.rs @@ -23,6 +23,9 @@ mod prefix; #[cfg(all(feature = "pretty-print", any(feature = "std", feature = "spin-lock")))] mod pretty_mismatches; +#[cfg(feature = "std")] +mod async_fn; + #[cfg(all(feature = "mock-core", feature = "mock-std"))] mod std; diff --git a/tests/nightly/async_fn_in_trait.rs b/tests/nightly/async_fn_in_trait.rs deleted file mode 100644 index d7974fe..0000000 --- a/tests/nightly/async_fn_in_trait.rs +++ /dev/null @@ -1,21 +0,0 @@ -use unimock::*; - -#[unimock(api = TraitMock)] -trait Trait { - async fn a(&self, arg: i32) -> i32; - async fn b(&self) -> &i32; - async fn c(&self) -> Option<&i32>; -} - -#[tokio::test] -async fn test_it() { - let deps = Unimock::new(( - TraitMock::a.next_call(matching!(_)).returns(42), - TraitMock::b.next_call(matching!()).returns(42), - TraitMock::c.next_call(matching!()).returns(Some(42)), - )); - - assert_eq!(42, deps.a(5).await); - assert_eq!(&42, deps.b().await); - assert_eq!(Some(&42), deps.c().await); -} diff --git a/tests/nightly/main.rs b/tests/nightly/main.rs index a28cf6f..ddc8fce 100644 --- a/tests/nightly/main.rs +++ b/tests/nightly/main.rs @@ -6,7 +6,6 @@ #![allow(clippy::disallowed_names)] mod associated_future; -mod async_fn_in_trait; mod rpit_future; fn main() {} diff --git a/tests/nightly/rpit_future.rs b/tests/nightly/rpit_future.rs deleted file mode 100644 index b7584e7..0000000 --- a/tests/nightly/rpit_future.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::future::Future; - -use unimock::*; - -#[unimock(api = RpitFutureMock)] -trait RpitFuture { - fn m1(&self) -> impl Future; - fn m2(&self, arg: i32) -> impl Send + Future; - fn m3(&self) -> impl Future + Send; - fn m4(&self) -> impl core::future::Future + Send; -} - -#[tokio::test] -async fn rpit() { - let u = Unimock::new(( - RpitFutureMock::m1.next_call(matching!()).returns(1337), - RpitFutureMock::m2.next_call(matching!(42)).returns(1338), - )); - - assert_eq!(u.m1().await, 1337); - assert_eq!(u.m2(42).await, 1338); -}