diff --git a/CHANGELOG.md b/CHANGELOG.md index cf8952a7..07e960fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate ### Added +- Added the ability to mock methods returning references to trait objects. + ([#213](https://github.com/asomers/mockall/pull/213)) + - `mock!` supports a new syntax: "impl Trait for". It has two benefits: * It can implement a generic trait for specific generic type(s). * It allows mocking a non-local trait. diff --git a/mockall/src/lib.rs b/mockall/src/lib.rs index eb3e3743..468a99f5 100644 --- a/mockall/src/lib.rs +++ b/mockall/src/lib.rs @@ -507,6 +507,25 @@ //! assert_eq!("abcd", mock.name()); //! ``` //! +//! Similarly, Mockall will use a Boxed trait object for the Expectation of +//! methods that return references to trait objects. +//! +//! ``` +//! # use mockall::*; +//! # use std::fmt::Display; +//! #[automock] +//! trait Foo { +//! fn name(&self) -> &dyn Display; +//! } +//! +//! # fn main() { +//! let mut mock = MockFoo::new(); +//! mock.expect_name().return_const(Box::new("abcd")); +//! assert_eq!("abcd", format!("{}", mock.name())); +//! # } +//! ``` +//! +//! //! ## Impl Trait //! //! Rust 1.26.0 introduced the `impl Trait` feature. It allows functions to diff --git a/mockall/tests/mock_return_dyn_trait.rs b/mockall/tests/mock_return_dyn_trait.rs new file mode 100644 index 00000000..6011791c --- /dev/null +++ b/mockall/tests/mock_return_dyn_trait.rs @@ -0,0 +1,49 @@ +// vim: tw=80 +//! a method that returns a reference to a trait object +#![deny(warnings)] + +use mockall::*; +use std::fmt::Debug; + +trait T: Debug + Sync { + fn mutate(&mut self) {} +} + +impl T for u32 {} + +impl T for Q where Q: Debug + Sync + AsMut {} + +mock!{ + Foo { + fn foo(&self) -> &dyn Debug; + fn bar(&self) -> &'static dyn T; + fn baz(&mut self) -> &mut dyn T; + } +} + +#[test] +fn return_const() { + let mut mock = MockFoo::new(); + mock.expect_foo() + .return_const(Box::new(42u32) as Box); + + assert_eq!("42", format!("{:?}", mock.foo())); +} + +#[test] +fn static_ref() { + let mut mock = MockFoo::new(); + mock.expect_bar() + .return_const(&42u32 as &dyn T); + + assert_eq!("42", format!("{:?}", mock.bar())); +} + +#[test] +fn return_var() { + let mut mock = MockFoo::new(); + mock.expect_baz() + .return_var(Box::new(42u32) as Box); + + mock.baz().mutate(); +} diff --git a/mockall_derive/src/mock_function.rs b/mockall_derive/src/mock_function.rs index dba77b5a..97ef88b4 100644 --- a/mockall_derive/src/mock_function.rs +++ b/mockall_derive/src/mock_function.rs @@ -3,6 +3,29 @@ use super::*; use quote::ToTokens; +/// Convert a trait object reference into a reference to a Boxed trait +fn dedynify(ty: &mut Type) { + if let Type::Reference(ref mut tr) = ty { + if let Type::TraitObject(ref tto) = tr.elem.as_ref() { + if let Some(lt) = &tr.lifetime { + if lt.ident == "static" { + // For methods that return 'static references, the user can + // usually actually supply one, unlike nonstatic references. + // dedynify is unneeded and harmful in such cases. + // + // But we do need to add parens to prevent parsing errors + // when methods like returning add a `+ Send` to the output + // type. + *tr.elem = parse2(quote!((#tto))).unwrap(); + return; + } + } + + *tr.elem = parse2(quote!(Box<#tto>)).unwrap(); + } + } +} + /// Convert a special reference type like "&str" into a reference to its owned /// type like "&String". fn destrify(ty: &mut Type) { @@ -206,6 +229,7 @@ impl<'a> Builder<'a> { ReturnType::Type(_, ref ty) => { let mut output_ty = supersuperfy(&**ty, self.levels); destrify(&mut output_ty); + dedynify(&mut output_ty); output_ty } };