Skip to content

Commit

Permalink
Fix mocking generic traits with generic methods
Browse files Browse the repository at this point in the history
For a generic method of a generic trait or struct, both sets of generics
must be excluded in expectation!
  • Loading branch information
asomers committed Jul 10, 2019
1 parent 7aa4d9e commit 6126359
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 6 deletions.
65 changes: 59 additions & 6 deletions mockall_derive/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ fn gen_mock_method(mod_ident: Option<&syn::Ident>,
let fn_token = &sig.decl.fn_token;
let ident = &sig.ident;
let (ig, tg, wc) = sig.decl.generics.split_for_impl();
let call_turbofish = tg.as_turbofish();
let merged_g = merge_generics(&generics, &sig.decl.generics);
let inputs = demutify(&sig.decl.inputs);
let output = &sig.decl.output;
let attrs = format_attrs(meth_attrs);
Expand Down Expand Up @@ -265,12 +265,15 @@ fn gen_mock_method(mod_ident: Option<&syn::Ident>,
}
}

let tg = if meth_types.is_static || !sig.decl.generics.params.is_empty() {
// For generic and static methods only, the trait's generic parameters
// become generic parameters of the method.
merged_g.split_for_impl().1
} else {
tg
};
let call_turbofish = tg.as_turbofish();
if meth_types.is_static {
// For static methods only, the trait's generic parameters become
// generic parameters of the method.
let merged_g = merge_generics(&generics, &sig.decl.generics);
let (_, tg, _) = merged_g.split_for_impl();
let call_turbofish = tg.as_turbofish();
quote!({
#expect_obj_name.lock().unwrap().#call#call_turbofish(#(#args),*)
})
Expand Down Expand Up @@ -944,6 +947,56 @@ mod t {
check(desired, code);
}

/// Mocking a generic struct that also has a generic method
#[test]
fn generic_struct_with_generic_method() {
let desired = r#"
#[allow(non_snake_case)]
pub mod __mock_Foo {
use super:: * ;
::mockall::expectation!{
pub fn foo<T, Q>(&self) -> () {
let () = ();
}
}
}
pub struct MockFoo<T: Clone + 'static> {
foo: __mock_Foo::foo::GenericExpectations ,
_t0: ::std::marker::PhantomData<T> ,
}
impl<T: Clone + 'static> ::std::default::Default for MockFoo<T> {
fn default() -> Self {
Self {
foo: __mock_Foo::foo::GenericExpectations::default(),
_t0: ::std::marker::PhantomData,
}
}
}
impl<T: Clone + 'static> MockFoo<T> {
pub fn foo<Q: 'static>(&self) {
self.foo.call:: <T, Q>()
}
pub fn expect_foo<Q: 'static>(&mut self)
-> &mut __mock_Foo::foo::Expectation<T, Q>
{
self.foo.expect:: <T, Q>()
}
pub fn checkpoint(&mut self) {
{ self.foo.checkpoint(); }
}
pub fn new() -> Self {
Self::default()
}
}
"#;
let code = r#"
pub Foo<T: Clone + 'static> {
fn foo<Q: 'static>(&self);
}
"#;
check(desired, code);
}

/// Implement a generic trait on a generic struct with mock!
#[test]
fn generic_struct_with_generic_trait() {
Expand Down
19 changes: 19 additions & 0 deletions mockall_derive/tests/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,25 @@ mod generic_struct_with_non_default_parameter {
}
}

mod generic_struct_with_generic_method {
use super::*;

mock!{
pub Foo<T: Clone + 'static> {
fn foo<Q: 'static>(&self, q: Q) -> T;
}
}

#[test]
fn t() {
let mut mock = MockFoo::<u32>::new();
mock.expect_foo::<i16>()
.return_const(100_000u32);
assert_eq!(100_000, mock.foo(-5i16));
}

}

/// Use mock! to mock a generic struct
mod generic_struct_with_generic_trait {
use super::*;
Expand Down

0 comments on commit 6126359

Please sign in to comment.