diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f44b12b..99aa2e2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate ### Added +- When a test fails because of a method sequence violation, the error message + will now show the method's arguments. This requires the `nightly` feature, + and requires that the arguments implement `Debug`. + ([#247](https://github.com/asomers/mockall/pull/247)) + - When a test fails because a mock object receives an unexpected call, the error message will now show the method's arguments. This requires the `nightly` feature, and requires that the arguments implement `Debug`. diff --git a/mockall/src/lib.rs b/mockall/src/lib.rs index 188a804b..682b4f8e 100644 --- a/mockall/src/lib.rs +++ b/mockall/src/lib.rs @@ -1534,8 +1534,8 @@ impl SeqHandle { } /// Verify that this handle was called in the correct order - pub fn verify(&self) { - self.inner.verify(self.seq); + pub fn verify(&self, desc: &str) { + self.inner.verify(self.seq, desc); } } @@ -1552,9 +1552,9 @@ impl SeqInner { } /// Verify that the call identified by `seq` was called in the correct order - fn verify(&self, seq: usize) { + fn verify(&self, seq: usize, desc: &str) { assert_eq!(seq, self.satisfaction_level.load(Ordering::Relaxed), - "Method sequence violation") + "{}: Method sequence violation", desc) } } diff --git a/mockall/tests/mock_return_mutable_reference.rs b/mockall/tests/mock_return_mutable_reference.rs index 318dd791..9838fcfe 100644 --- a/mockall/tests/mock_return_mutable_reference.rs +++ b/mockall/tests/mock_return_mutable_reference.rs @@ -42,3 +42,51 @@ fn returning() { let r = mock.foo(0); assert_eq!(5, *r); } + +mod sequence { + use super::*; + + #[test] + #[cfg_attr(feature = "nightly", + should_panic(expected = "MockFoo::foo(4): Method sequence violation"))] + #[cfg_attr(not(feature = "nightly"), + should_panic(expected = "MockFoo::foo(?): Method sequence violation"))] + fn fail() { + let mut seq = Sequence::new(); + let mut mock = MockFoo::new(); + mock.expect_foo() + .with(predicate::eq(3)) + .times(1) + .return_var(0) + .in_sequence(&mut seq); + + mock.expect_foo() + .with(predicate::eq(4)) + .times(1) + .return_var(0) + .in_sequence(&mut seq); + + mock.foo(4); + } + + #[test] + fn ok() { + let mut seq = Sequence::new(); + let mut mock = MockFoo::new(); + mock.expect_foo() + .with(predicate::eq(3)) + .times(1) + .return_var(0) + .in_sequence(&mut seq); + + mock.expect_foo() + .with(predicate::eq(4)) + .times(1) + .return_var(0) + .in_sequence(&mut seq); + + mock.foo(3); + mock.foo(4); + } + +} diff --git a/mockall/tests/mock_return_reference.rs b/mockall/tests/mock_return_reference.rs index 08e63ea4..e8c79696 100644 --- a/mockall/tests/mock_return_reference.rs +++ b/mockall/tests/mock_return_reference.rs @@ -6,7 +6,7 @@ use mockall::*; mock! { Foo { - fn foo(&self) -> &u32; + fn foo(&self, x: i32) -> &u32; fn bar(&self) -> &u32; } } @@ -38,7 +38,7 @@ fn return_const() { let mut mock = MockFoo::new(); mock.expect_foo() .return_const(5u32); - assert_eq!(5, *mock.foo()); + assert_eq!(5, *mock.foo(4)); } #[test] @@ -48,7 +48,7 @@ fn return_const() { fn return_default() { let mut mock = MockFoo::new(); mock.expect_foo(); - let r = mock.foo(); + let r = mock.foo(4); assert_eq!(u32::default(), *r); } @@ -63,11 +63,14 @@ mod sequence { mock.expect_foo() .times(1..3) .in_sequence(&mut seq); - mock.foo(); + mock.foo(4); } #[test] - #[should_panic(expected = "Method sequence violation")] + #[cfg_attr(feature = "nightly", + should_panic(expected = "MockFoo::foo(4): Method sequence violation"))] + #[cfg_attr(not(feature = "nightly"), + should_panic(expected = "MockFoo::foo(?): Method sequence violation"))] fn fail() { let mut seq = Sequence::new(); let mut mock = MockFoo::new(); @@ -81,7 +84,7 @@ mod sequence { .return_const(0) .in_sequence(&mut seq); - mock.foo(); + mock.foo(4); mock.bar(); } @@ -99,7 +102,7 @@ mod sequence { .return_const(0) .in_sequence(&mut seq); - mock.foo(); + mock.foo(4); mock.bar(); } } diff --git a/mockall/tests/mock_struct.rs b/mockall/tests/mock_struct.rs index ece8584b..716875a8 100644 --- a/mockall/tests/mock_struct.rs +++ b/mockall/tests/mock_struct.rs @@ -236,7 +236,7 @@ mod sequence { } #[test] - #[should_panic(expected = "Method sequence violation")] + #[should_panic(expected = "MockFoo::baz(): Method sequence violation")] fn fail() { let mut seq = Sequence::new(); let mut mock = MockFoo::new(); diff --git a/mockall_derive/src/mock_function.rs b/mockall_derive/src/mock_function.rs index adcd2b86..f2d4fc0f 100644 --- a/mockall_derive/src/mock_function.rs +++ b/mockall_derive/src/mock_function.rs @@ -432,17 +432,9 @@ impl MockFunction { }.split_for_impl(); let tbf = tg.as_turbofish(); let name = self.name(); - let argnames = &self.argnames; - let fields = vec!["{:?}"; argnames.len()].join(", "); - let fstr = if let Some(s) = &self.struct_ { - format!("{}::{}({}): No matching expectation found", - s, name, fields) - } else { - format!("{}::{}({}): No matching expectation found", - self.mod_ident, self.name(), fields) - }; - let no_match_msg = quote!(format!(#fstr, - #(::mockall::MaybeDebugger(&#argnames)),*)); + let desc = self.desc(); + let no_match_msg = quote!(format!("{}: No matching expectation found", + #desc)); let sig = &self.sig; let vis = if self.trait_.is_some() { &Visibility::Inherited @@ -546,6 +538,19 @@ impl MockFunction { ) } + /// Generate a code fragment that will print a description of the invocation + fn desc(&self) -> impl ToTokens { + let argnames = &self.argnames; + let name = if let Some(s) = &self.struct_ { + format!("{}::{}", s, self.sig.ident) + } else { + format!("{}::{}", self.mod_ident, self.sig.ident) + }; + let fields = vec!["{:?}"; argnames.len()].join(", "); + let fstr = format!("{}({})", name, fields); + quote!(format!(#fstr, #(::mockall::MaybeDebugger(&#argnames)),*)) + } + /// Generate code for the expect_ method /// /// # Arguments @@ -816,7 +821,7 @@ impl<'a> ToTokens for Common<'a> { } impl #ig Common #tg #wc { - fn call(&self) { + fn call(&self, desc: &str) { self.times.call() .unwrap_or_else(|m| { let desc = format!("{}", @@ -824,7 +829,7 @@ impl<'a> ToTokens for Common<'a> { panic!("{}: Expectation({}) {}", #funcname, desc, m); }); - self.verify_sequence(); + self.verify_sequence(desc); if self.times.is_satisfied() { self.satisfy_sequence() } @@ -894,9 +899,9 @@ impl<'a> ToTokens for Common<'a> { ); } - fn verify_sequence(&self) { + fn verify_sequence(&self, desc: &str) { if let Some(__mockall_handle) = &self.seq_handle { - __mockall_handle.verify() + __mockall_handle.verify(desc) } } } @@ -1811,7 +1816,10 @@ struct RefExpectation<'a> { impl<'a> ToTokens for RefExpectation<'a> { fn to_tokens(&self, tokens: &mut TokenStream) { + let argnames = &self.f.argnames; + let argty = &self.f.argty; let common_methods = CommonExpectationMethods{f: &self.f}; + let desc = self.f.desc(); let funcname = self.f.funcname(); let (ig, tg, wc) = self.f.egenerics.split_for_impl(); @@ -1832,9 +1840,9 @@ impl<'a> ToTokens for RefExpectation<'a> { #[allow(clippy::unused_unit)] impl #ig Expectation #tg #wc { /// Call this [`Expectation`] as if it were the real method. - #v fn call #lg (&self) -> #output + #v fn call #lg (&self, #(#argnames: #argty, )*) -> #output { - self.common.call(); + self.common.call(&#desc); self.rfunc.call().unwrap_or_else(|m| { let desc = format!("{}", self.common.matcher.lock().unwrap()); @@ -1876,6 +1884,7 @@ impl<'a> ToTokens for RefMutExpectation<'a> { let common_methods = CommonExpectationMethods{f: &self.f}; let argnames = &self.f.argnames; let argty = &self.f.argty; + let desc = self.f.desc(); let funcname = self.f.funcname(); let (ig, tg, wc) = self.f.egenerics.split_for_impl(); let (_, common_tg, _) = self.f.cgenerics.split_for_impl(); @@ -1897,7 +1906,7 @@ impl<'a> ToTokens for RefMutExpectation<'a> { #v fn call_mut #lg (&mut self, #(#argnames: #argty, )*) -> &mut #owned_output { - self.common.call(); + self.common.call(&#desc); let desc = format!("{}", self.common.matcher.lock().unwrap()); self.rfunc.call_mut(#(#argnames, )*).unwrap_or_else(|m| { @@ -1962,6 +1971,7 @@ impl<'a> ToTokens for StaticExpectation<'a> { let common_methods = CommonExpectationMethods{f: &self.f}; let argnames = &self.f.argnames; let argty = &self.f.argty; + let desc = self.f.desc(); let hrtb = self.f.hrtb(); let funcname = self.f.funcname(); let (ig, tg, wc) = self.f.egenerics.split_for_impl(); @@ -1969,6 +1979,7 @@ impl<'a> ToTokens for StaticExpectation<'a> { let lg = lifetimes_to_generics(&self.f.alifetimes); let output = &self.f.output; let v = &self.f.privmod_vis; + quote!( /// Expectation type for methods that return a `'static` type. /// This is the type returned by the `expect_*` methods. @@ -1983,7 +1994,7 @@ impl<'a> ToTokens for StaticExpectation<'a> { #[doc(hidden)] #v fn call #lg (&self, #(#argnames: #argty, )* ) -> #output { - self.common.call(); + self.common.call(&#desc); self.rfunc.lock().unwrap().call_mut(#(#argnames, )*) .unwrap_or_else(|message| { let desc = format!("{}", @@ -2152,7 +2163,7 @@ impl<'a> ToTokens for RefExpectations<'a> { __mockall_e.matches(#(#predexprs, )*) && (!__mockall_e.is_done() || self.0.len() == 1)) .map(move |__mockall_e| - __mockall_e.call() + __mockall_e.call(#(#argnames),*) ) }