From 99647b6137ea8fff11a35fbf3abc3c6faf0e7f36 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Wed, 4 Nov 2020 20:05:27 -0700 Subject: [PATCH] Automock methods returning "impl Future" Unlike other traits, these two aren't very useful in a `Box`. Instead, Mockall will now change the Expectation's return type to `Pin>`. Fixes #220 --- CHANGELOG.md | 5 ++ mockall/src/lib.rs | 28 +++++++ .../automock_associated_type_constructor.rs | 13 ++-- mockall/tests/automock_impl_future.rs | 50 ++++++++++++ mockall_derive/src/lib.rs | 78 ++++++++++++++++++- 5 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 mockall/tests/automock_impl_future.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d1bbdd5..574e935b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate ### Added +- Added the ability to mock methods returning `impl Future` or `impl Stream`. + Unlike other traits, these two aren't very useful in a `Box`. Instead, + Mockall will now change the Expectation's return type to `Pin>`. + ([#229](https://github.com/asomers/mockall/pull/229)) + - Added the ability to mock methods returning references to trait objects. ([#213](https://github.com/asomers/mockall/pull/213)) diff --git a/mockall/src/lib.rs b/mockall/src/lib.rs index 468a99f5..567321a1 100644 --- a/mockall/src/lib.rs +++ b/mockall/src/lib.rs @@ -595,6 +595,34 @@ //! //! See Also [`impl-trait-for-returning-complex-types-with-ease.html`](https://rust-lang-nursery.github.io/edition-guide/rust-2018/trait-system/impl-trait-for-returning-complex-types-with-ease) //! +//! ### impl Future +//! +//! Rust 1.36.0 added the `Future` trait. Unlike virtually every trait that +//! preceeded it, `Box` is mostly useless. Instead, you usually +//! need a `Pin>`. So that's what Mockall will do when you mock +//! a method returning `impl Future` or the related `impl Stream`. Just +//! remember to use `pin` in your expectations, like this: +//! +//! ``` +//! # use mockall::*; +//! # use std::fmt::Debug; +//! # use futures::{Future, future}; +//! struct Foo {} +//! #[automock] +//! impl Foo { +//! fn foo(&self) -> impl Future { +//! // ... +//! # future::ready(42) +//! } +//! } +//! +//! # fn main() { +//! let mut mock = MockFoo::new(); +//! mock.expect_foo() +//! .returning(|| Box::pin(future::ready(42))); +//! # } +//! ``` +//! //! ## Mocking structs //! //! Mockall mocks structs as well as traits. The problem here is a namespace diff --git a/mockall/tests/automock_associated_type_constructor.rs b/mockall/tests/automock_associated_type_constructor.rs index 509a22b8..a910ffa4 100644 --- a/mockall/tests/automock_associated_type_constructor.rs +++ b/mockall/tests/automock_associated_type_constructor.rs @@ -5,9 +5,8 @@ use mockall::*; -pub trait Future { +pub trait MyIterator { type Item; - type Error; } #[allow(unused)] @@ -15,11 +14,10 @@ pub struct Foo{} #[automock] impl Foo { - pub fn open() -> impl Future { + pub fn open() -> impl MyIterator { struct Bar {} - impl Future for Bar { + impl MyIterator for Bar { type Item=Foo; - type Error=i32; } Bar{} } @@ -30,9 +28,8 @@ fn returning() { let ctx = MockFoo::open_context(); ctx.expect().returning(|| { struct Baz {} - impl Future for Baz { - type Item=MockFoo; - type Error=i32; + impl MyIterator for Baz { + type Item = MockFoo; } Box::new(Baz{}) }); diff --git a/mockall/tests/automock_impl_future.rs b/mockall/tests/automock_impl_future.rs new file mode 100644 index 00000000..859fa08d --- /dev/null +++ b/mockall/tests/automock_impl_future.rs @@ -0,0 +1,50 @@ +// vim: tw=80 +//! A trait with a constructor method that returns impl Future<...>. +//! +//! This needs special handling, because Box> is pretty useless. +//! You need Pin>> instead. +#![deny(warnings)] + +use futures::{Future, FutureExt, Stream, StreamExt, future, stream}; +use mockall::*; + +pub struct Foo{} + +#[automock] +impl Foo { + pub fn foo(&self) -> impl Future + { + future::ready(42) + } + + pub fn bar(&self) -> impl Stream + { + stream::empty() + } +} + +#[test] +fn returning_future() { + let mut mock = MockFoo::new(); + mock.expect_foo() + .returning(|| { + Box::pin(future::ready(42)) + }); + mock.foo() + .now_or_never() + .unwrap(); +} + +#[test] +fn returning_stream() { + let mut mock = MockFoo::new(); + mock.expect_bar() + .returning(|| { + Box::pin(stream::iter(vec![42].into_iter())) + }); + let all = mock.bar() + .collect::>() + .now_or_never() + .unwrap(); + assert_eq!(&all[..], &[42][..]); +} diff --git a/mockall_derive/src/lib.rs b/mockall_derive/src/lib.rs index 61f6cdc6..841eb067 100644 --- a/mockall_derive/src/lib.rs +++ b/mockall_derive/src/lib.rs @@ -246,12 +246,31 @@ fn declosurefy(gen: &Generics, args: &Punctuated) -> (outg, outargs, callargs) } -/// Replace any "impl trait" types with "Box" equivalents +/// Replace any "impl trait" types with "Box" or equivalent. fn deimplify(rt: &mut ReturnType) { if let ReturnType::Type(_, ty) = rt { if let Type::ImplTrait(ref tit) = &**ty { + let needs_pin = tit.bounds + .iter() + .any(|tpb| { + if let TypeParamBound::Trait(tb) = tpb { + if let Some(seg) = tb.path.segments.last() { + seg.ident == "Future" || seg.ident == "Stream" + } else { + // It might still be a Future, but we can't guess + // what names it might be imported under. Too bad. + false + } + } else { + false + } + }); let bounds = &tit.bounds; - *ty = parse2(quote!(Box)).unwrap(); + if needs_pin { + *ty = parse2(quote!(::std::pin::Pin>)).unwrap(); + } else { + *ty = parse2(quote!(Box)).unwrap(); + } } } } @@ -1133,6 +1152,61 @@ mod automock { } } +mod deimplify { + use super::*; + + fn check_deimplify(orig_ts: TokenStream, expected_ts: TokenStream) { + let mut orig: ReturnType = parse2(orig_ts).unwrap(); + let expected: ReturnType = parse2(expected_ts).unwrap(); + deimplify(&mut orig); + assert_eq!(quote!(#orig).to_string(), quote!(#expected).to_string()); + } + + // Future is a special case + #[test] + fn impl_future() { + check_deimplify( + quote!(-> impl Future), + quote!(-> ::std::pin::Pin>>) + ); + } + + // Future is a special case, wherever it appears + #[test] + fn impl_future_reverse() { + check_deimplify( + quote!(-> impl Send + Future), + quote!(-> ::std::pin::Pin>>) + ); + } + + // Stream is a special case + #[test] + fn impl_stream() { + check_deimplify( + quote!(-> impl Stream), + quote!(-> ::std::pin::Pin>>) + ); + } + + #[test] + fn impl_trait() { + check_deimplify( + quote!(-> impl Foo), + quote!(-> Box) + ); + } + + // With extra bounds + #[test] + fn impl_trait2() { + check_deimplify( + quote!(-> impl Foo + Send), + quote!(-> Box) + ); + } +} + mod deselfify { use super::*;