Skip to content

Commit

Permalink
add test around several mocks for different concrete types + add gene…
Browse files Browse the repository at this point in the history
…ric type name to the error message in case a stub is missing
  • Loading branch information
o0Ignition0o authored and Jeremy Lempereur committed Nov 23, 2024
1 parent e95c783 commit f0eead8
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 28 deletions.
45 changes: 30 additions & 15 deletions faux_macros/src/methods/morphed.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{methods::receiver::Receiver, self_type::SelfType};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{spanned::Spanned, Generics, PathArguments, Type, TypePath};
use syn::{spanned::Spanned, Generics, Ident, PathArguments, Type, TypePath};

pub struct Signature<'a> {
name: &'a syn::Ident,
Expand Down Expand Up @@ -152,11 +152,12 @@ impl<'a> Signature<'a> {
.as_ref()
.map(|method_data| method_data.generics.clone());

let maybe_generics = generic_types_only(generics);
let generic_idents = generic_type_idents(generics);
let turbofish = turbofish(&generic_idents);

let proxy = match self.trait_path {
None => quote! { <#real_ty>::#name #maybe_generics },
Some(path) => quote! { <#real_ty as #path>::#name #maybe_generics },
None => quote! { <#real_ty>::#name #turbofish },
Some(path) => quote! { <#real_ty as #path>::#name #turbofish },
};

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

let fn_name = name.to_string();
let mut generics_str = generic_idents
.into_iter()
.map(|i| i.to_string())
.collect::<Vec<_>>()
.join(",");
generics_str.retain(|c| !c.is_whitespace());

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

let generics_where_clause = &generics.where_clause;

let maybe_generics = generic_types_only(Some(generics.clone()));
let generic_idents = generic_type_idents(Some(generics.clone()));
let turbofish = turbofish(&generic_idents);

let when_method = syn::parse_quote! {
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 {
match &mut self.0 {
faux::MaybeFaux::Faux(_maybe_faux_faux) => faux::When::new(
<Self>::#faux_ident #maybe_generics,
<Self>::#faux_ident #turbofish,
#name_str,
_maybe_faux_faux
),
Expand Down Expand Up @@ -490,14 +498,21 @@ fn path_args_contains_self(path: &syn::Path, self_path: &syn::TypePath) -> bool
}
}

fn generic_types_only(generics: Option<Generics>) -> TokenStream {
if let Some(mut g) = generics {
let type_params = g
.type_params_mut()
.into_iter()
.map(|type_param| type_param.ident.clone());
quote! { :: < #(#type_params),* > }
} else {
fn generic_type_idents(generics: Option<Generics>) -> Vec<Ident> {
generics
.map(|g| {
g.type_params()
.into_iter()
.map(|tp| tp.ident.clone())
.collect()
})
.unwrap_or_default()
}

fn turbofish(idents: &[Ident]) -> TokenStream {
if idents.is_empty() {
quote! {}
} else {
quote! { :: < #(#idents),* > }
}
}
28 changes: 19 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -999,11 +999,13 @@ impl Faux {
id: fn(R, I) -> O,
fn_name: &'static str,
input: I,
generics: &'static str,
) -> Result<O, InvocationError> {
let mock = self.store.get(id, fn_name)?;
let mock = self.store.get(id, fn_name, generics)?;
mock.call(input).map_err(|stub_error| InvocationError {
fn_name: mock.name(),
fn_name: fn_name,
struct_name: self.store.struct_name,
generics,
stub_error,
})
}
Expand All @@ -1012,22 +1014,30 @@ impl Faux {
pub struct InvocationError {
struct_name: &'static str,
fn_name: &'static str,
generics: &'static str,
stub_error: mock::InvocationError,
}

impl fmt::Display for InvocationError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let generics = if self.generics.is_empty() {
String::new()
} else {
format!("<{}>", self.generics)
};
match &self.stub_error {
mock::InvocationError::NeverStubbed => write!(
f,
"`{}::{}` was called but never stubbed",
self.struct_name, self.fn_name
),
mock::InvocationError::NeverStubbed => {
write!(
f,
"`{}::{}{}` was called but never stubbed",
self.struct_name, self.fn_name, generics
)
}
mock::InvocationError::Stub(errors) => {
writeln!(
f,
"`{}::{}` had no suitable stubs. Existing stubs failed because:",
self.struct_name, self.fn_name
"`{}::{}{}` had no suitable stubs. Existing stubs failed because:",
self.struct_name, self.fn_name, generics
)?;
let mut errors = errors.iter();
if let Some(e) = errors.next() {
Expand Down
2 changes: 2 additions & 0 deletions src/mock/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl<'stub> Store<'stub> {
&self,
id: fn(R, I) -> O,
fn_name: &'static str,
generics: &'static str,
) -> Result<&Mock<'stub, I, O>, InvocationError> {
match self.stubs.get(&(id as usize)).map(|m| m.as_typed()) {
Some(mock) => {
Expand All @@ -53,6 +54,7 @@ impl<'stub> Store<'stub> {
None => Err(InvocationError {
fn_name,
struct_name: self.struct_name,
generics,
stub_error: super::InvocationError::NeverStubbed,
}),
}
Expand Down
22 changes: 22 additions & 0 deletions tests/generic_method_return.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ pub trait MyTrait {}
struct Entity {}
impl MyTrait for Entity {}

#[derive(Clone, PartialEq, Debug)]
struct Entity2 {}
impl MyTrait for Entity2 {}

#[faux::create]
pub struct Foo {}

Expand Down Expand Up @@ -112,3 +116,21 @@ fn generic_tests_async() {
assert_eq!(qux_with_arg.qux_with_arg::<Entity>(50).await, 100);
});
}

#[test]
fn generic_two_different_impls() {
let mut qux_with_arg = AsyncFoo::faux();
faux::when!(qux_with_arg.qux_with_arg::<Entity>()).then(|_| 100);
faux::when!(qux_with_arg.qux_with_arg::<Entity2>()).then(|_| 200);
futures::executor::block_on(async {
assert_eq!(qux_with_arg.qux_with_arg::<Entity>(42).await, 100);
assert_eq!(qux_with_arg.qux_with_arg::<Entity2>(42).await, 200);
});
}

#[test]
#[should_panic(expected = "`Foo::qux<E>` was called but never stubbed")]
fn unmocked_faux_panics_with_generic_information() {
let foo = Foo::faux();
foo.qux::<Entity>();
}
2 changes: 1 addition & 1 deletion tests/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ fn faux_ref_output() {
}

#[test]
#[should_panic]
#[should_panic(expected = "`Foo::get_stuff` was called but never stubbed")]
fn unmocked_faux_panics() {
let mock = Foo::faux();
mock.get_stuff();
Expand Down
3 changes: 0 additions & 3 deletions tests/when_arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ impl Foo {
}
}

#[derive(Debug)]
struct Bar(i32);

#[test]
fn no_args() {
let mut mock = Foo::faux();
Expand Down

0 comments on commit f0eead8

Please sign in to comment.