Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better error messages for Sequence violations #247

Merged
merged 1 commit into from
Jan 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
8 changes: 4 additions & 4 deletions mockall/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand All @@ -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)
}
}

Expand Down
48 changes: 48 additions & 0 deletions mockall/tests/mock_return_mutable_reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

}
17 changes: 10 additions & 7 deletions mockall/tests/mock_return_reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use mockall::*;

mock! {
Foo {
fn foo(&self) -> &u32;
fn foo(&self, x: i32) -> &u32;
fn bar(&self) -> &u32;
}
}
Expand Down Expand Up @@ -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]
Expand All @@ -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);
}

Expand All @@ -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();
Expand All @@ -81,7 +84,7 @@ mod sequence {
.return_const(0)
.in_sequence(&mut seq);

mock.foo();
mock.foo(4);
mock.bar();
}

Expand All @@ -99,7 +102,7 @@ mod sequence {
.return_const(0)
.in_sequence(&mut seq);

mock.foo();
mock.foo(4);
mock.bar();
}
}
2 changes: 1 addition & 1 deletion mockall/tests/mock_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
51 changes: 31 additions & 20 deletions mockall_derive/src/mock_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -816,15 +821,15 @@ 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!("{}",
self.matcher.lock().unwrap());
panic!("{}: Expectation({}) {}", #funcname, desc,
m);
});
self.verify_sequence();
self.verify_sequence(desc);
if self.times.is_satisfied() {
self.satisfy_sequence()
}
Expand Down Expand Up @@ -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)
}
}
}
Expand Down Expand Up @@ -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();

Expand All @@ -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());
Expand Down Expand Up @@ -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();
Expand All @@ -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| {
Expand Down Expand Up @@ -1962,13 +1971,15 @@ 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();
let (_, common_tg, _) = self.f.cgenerics.split_for_impl();
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.
Expand All @@ -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!("{}",
Expand Down Expand Up @@ -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),*)
)
}

Expand Down