Skip to content

Commit f0eead8

Browse files
o0Ignition0oJeremy Lempereur
authored andcommitted
add test around several mocks for different concrete types + add generic type name to the error message in case a stub is missing
1 parent e95c783 commit f0eead8

File tree

6 files changed

+74
-28
lines changed

6 files changed

+74
-28
lines changed

faux_macros/src/methods/morphed.rs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{methods::receiver::Receiver, self_type::SelfType};
22
use proc_macro2::TokenStream;
33
use quote::{quote, ToTokens};
4-
use syn::{spanned::Spanned, Generics, PathArguments, Type, TypePath};
4+
use syn::{spanned::Spanned, Generics, Ident, PathArguments, Type, TypePath};
55

66
pub struct Signature<'a> {
77
name: &'a syn::Ident,
@@ -152,11 +152,12 @@ impl<'a> Signature<'a> {
152152
.as_ref()
153153
.map(|method_data| method_data.generics.clone());
154154

155-
let maybe_generics = generic_types_only(generics);
155+
let generic_idents = generic_type_idents(generics);
156+
let turbofish = turbofish(&generic_idents);
156157

157158
let proxy = match self.trait_path {
158-
None => quote! { <#real_ty>::#name #maybe_generics },
159-
Some(path) => quote! { <#real_ty as #path>::#name #maybe_generics },
159+
None => quote! { <#real_ty>::#name #turbofish },
160+
Some(path) => quote! { <#real_ty as #path>::#name #turbofish },
160161
};
161162

162163
let real_self_arg = self.method_data.as_ref().map(|_| {
@@ -214,10 +215,16 @@ impl<'a> Signature<'a> {
214215
};
215216

216217
let fn_name = name.to_string();
218+
let mut generics_str = generic_idents
219+
.into_iter()
220+
.map(|i| i.to_string())
221+
.collect::<Vec<_>>()
222+
.join(",");
223+
generics_str.retain(|c| !c.is_whitespace());
217224

218225
quote! {
219226
unsafe {
220-
match _maybe_faux_faux.call_stub(<Self>::#faux_ident #maybe_generics, #fn_name, #args) {
227+
match _maybe_faux_faux.call_stub(<Self>::#faux_ident #turbofish, #fn_name, #args, #generics_str) {
221228
std::result::Result::Ok(o) => o,
222229
std::result::Result::Err(e) => panic!("{}", e),
223230
}
@@ -366,13 +373,14 @@ impl<'a> MethodData<'a> {
366373

367374
let generics_where_clause = &generics.where_clause;
368375

369-
let maybe_generics = generic_types_only(Some(generics.clone()));
376+
let generic_idents = generic_type_idents(Some(generics.clone()));
377+
let turbofish = turbofish(&generic_idents);
370378

371379
let when_method = syn::parse_quote! {
372380
pub fn #when_ident<'m #maybe_comma #generics_contents>(&'m mut self) -> faux::When<'m, #receiver_ty, (#(#arg_types),*), #output, faux::matcher::AnyInvocation> #generics_where_clause {
373381
match &mut self.0 {
374382
faux::MaybeFaux::Faux(_maybe_faux_faux) => faux::When::new(
375-
<Self>::#faux_ident #maybe_generics,
383+
<Self>::#faux_ident #turbofish,
376384
#name_str,
377385
_maybe_faux_faux
378386
),
@@ -490,14 +498,21 @@ fn path_args_contains_self(path: &syn::Path, self_path: &syn::TypePath) -> bool
490498
}
491499
}
492500

493-
fn generic_types_only(generics: Option<Generics>) -> TokenStream {
494-
if let Some(mut g) = generics {
495-
let type_params = g
496-
.type_params_mut()
497-
.into_iter()
498-
.map(|type_param| type_param.ident.clone());
499-
quote! { :: < #(#type_params),* > }
500-
} else {
501+
fn generic_type_idents(generics: Option<Generics>) -> Vec<Ident> {
502+
generics
503+
.map(|g| {
504+
g.type_params()
505+
.into_iter()
506+
.map(|tp| tp.ident.clone())
507+
.collect()
508+
})
509+
.unwrap_or_default()
510+
}
511+
512+
fn turbofish(idents: &[Ident]) -> TokenStream {
513+
if idents.is_empty() {
501514
quote! {}
515+
} else {
516+
quote! { :: < #(#idents),* > }
502517
}
503518
}

src/lib.rs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -999,11 +999,13 @@ impl Faux {
999999
id: fn(R, I) -> O,
10001000
fn_name: &'static str,
10011001
input: I,
1002+
generics: &'static str,
10021003
) -> Result<O, InvocationError> {
1003-
let mock = self.store.get(id, fn_name)?;
1004+
let mock = self.store.get(id, fn_name, generics)?;
10041005
mock.call(input).map_err(|stub_error| InvocationError {
1005-
fn_name: mock.name(),
1006+
fn_name: fn_name,
10061007
struct_name: self.store.struct_name,
1008+
generics,
10071009
stub_error,
10081010
})
10091011
}
@@ -1012,22 +1014,30 @@ impl Faux {
10121014
pub struct InvocationError {
10131015
struct_name: &'static str,
10141016
fn_name: &'static str,
1017+
generics: &'static str,
10151018
stub_error: mock::InvocationError,
10161019
}
10171020

10181021
impl fmt::Display for InvocationError {
10191022
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1023+
let generics = if self.generics.is_empty() {
1024+
String::new()
1025+
} else {
1026+
format!("<{}>", self.generics)
1027+
};
10201028
match &self.stub_error {
1021-
mock::InvocationError::NeverStubbed => write!(
1022-
f,
1023-
"`{}::{}` was called but never stubbed",
1024-
self.struct_name, self.fn_name
1025-
),
1029+
mock::InvocationError::NeverStubbed => {
1030+
write!(
1031+
f,
1032+
"`{}::{}{}` was called but never stubbed",
1033+
self.struct_name, self.fn_name, generics
1034+
)
1035+
}
10261036
mock::InvocationError::Stub(errors) => {
10271037
writeln!(
10281038
f,
1029-
"`{}::{}` had no suitable stubs. Existing stubs failed because:",
1030-
self.struct_name, self.fn_name
1039+
"`{}::{}{}` had no suitable stubs. Existing stubs failed because:",
1040+
self.struct_name, self.fn_name, generics
10311041
)?;
10321042
let mut errors = errors.iter();
10331043
if let Some(e) = errors.next() {

src/mock/store.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ impl<'stub> Store<'stub> {
4444
&self,
4545
id: fn(R, I) -> O,
4646
fn_name: &'static str,
47+
generics: &'static str,
4748
) -> Result<&Mock<'stub, I, O>, InvocationError> {
4849
match self.stubs.get(&(id as usize)).map(|m| m.as_typed()) {
4950
Some(mock) => {
@@ -53,6 +54,7 @@ impl<'stub> Store<'stub> {
5354
None => Err(InvocationError {
5455
fn_name,
5556
struct_name: self.struct_name,
57+
generics,
5658
stub_error: super::InvocationError::NeverStubbed,
5759
}),
5860
}

tests/generic_method_return.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ pub trait MyTrait {}
66
struct Entity {}
77
impl MyTrait for Entity {}
88

9+
#[derive(Clone, PartialEq, Debug)]
10+
struct Entity2 {}
11+
impl MyTrait for Entity2 {}
12+
913
#[faux::create]
1014
pub struct Foo {}
1115

@@ -112,3 +116,21 @@ fn generic_tests_async() {
112116
assert_eq!(qux_with_arg.qux_with_arg::<Entity>(50).await, 100);
113117
});
114118
}
119+
120+
#[test]
121+
fn generic_two_different_impls() {
122+
let mut qux_with_arg = AsyncFoo::faux();
123+
faux::when!(qux_with_arg.qux_with_arg::<Entity>()).then(|_| 100);
124+
faux::when!(qux_with_arg.qux_with_arg::<Entity2>()).then(|_| 200);
125+
futures::executor::block_on(async {
126+
assert_eq!(qux_with_arg.qux_with_arg::<Entity>(42).await, 100);
127+
assert_eq!(qux_with_arg.qux_with_arg::<Entity2>(42).await, 200);
128+
});
129+
}
130+
131+
#[test]
132+
#[should_panic(expected = "`Foo::qux<E>` was called but never stubbed")]
133+
fn unmocked_faux_panics_with_generic_information() {
134+
let foo = Foo::faux();
135+
foo.qux::<Entity>();
136+
}

tests/simple.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ fn faux_ref_output() {
6969
}
7070

7171
#[test]
72-
#[should_panic]
72+
#[should_panic(expected = "`Foo::get_stuff` was called but never stubbed")]
7373
fn unmocked_faux_panics() {
7474
let mock = Foo::faux();
7575
mock.get_stuff();

tests/when_arguments.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@ impl Foo {
2828
}
2929
}
3030

31-
#[derive(Debug)]
32-
struct Bar(i32);
33-
3431
#[test]
3532
fn no_args() {
3633
let mut mock = Foo::faux();

0 commit comments

Comments
 (0)