Skip to content

Commit

Permalink
Allow mocking methods that return references to trait objects
Browse files Browse the repository at this point in the history
Fixes #210
  • Loading branch information
asomers committed Sep 20, 2020
1 parent dc88948 commit ab3f184
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
19 changes: 19 additions & 0 deletions mockall/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 49 additions & 0 deletions mockall/tests/mock_return_dyn_trait.rs
Original file line number Diff line number Diff line change
@@ -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<Q> T for Q where Q: Debug + Sync + AsMut<dyn T> {}

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<dyn Debug>);

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<dyn T>);

mock.baz().mutate();
}
24 changes: 24 additions & 0 deletions mockall_derive/src/mock_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}
};
Expand Down

0 comments on commit ab3f184

Please sign in to comment.