asomers / mockall

A powerful mock object library for Rust
Apache License 2.0
1.5k stars 62 forks source link

feature request: print expectations when no match found #441

Closed mattklein123 closed 1 year ago

mattklein123 commented 1 year ago

One thing I miss from gmock is that when there is no matching expectation, gmock prints the existing expectations and what was passed into the mock call to aid in debugging/fixing.

I would be happy to take a stab at implementing this if this sounds like an OK feature. Any quick implementation tips would be great also. Thank you.

asomers commented 1 year ago

Yeah, that would potentially be pretty nice, if it were possible. But it isn't. If it were, then here's what you should do. The changes are all in mockall_derive/mock_function.rs.

Now here's the catch: the most useful Matcher variants are Func and FuncSt, used with withf and withf_st, respectively. But they wrap functions or closures, which can't usefully be printed. And the other variant is Pred, used with with. That wraps a Box<dyn Predicate> type, which doesn't implement Debug. So there really isn't any useful way to print a Matcher.

What's left would be the ability to print a list of expectations along with the number of times they are expected, but that's not very useful compared to printing the Matchers.

Being able to print the individual Pred Matchers would require changing the inner type to Box<Predicate + MaybeDebugger>, and that would only work on nightly. Even then, it still wouldn't be possible to print the Func and FuncSt variants.

The reason this works in GoogleMock is because GoogleMock is all based on macros, which can stringify their arguments. But Mockall isn't based on macros.

Would it be useful to add an optional user-defined tag to each expectation, and print the list of unmatched expectations along with their tags? It would be like this:

let mut mock = MyMock::new();
mock.expect_foo()
     .with(eq(5))
    .times(2)
    .tag("five twice")
    .return_const(100);
;
mattklein123 commented 1 year ago

Thanks for the really detailed explanation. I'm still learning the ins and outs of rust and this issue is a bit more advanced. What you say makes sense. A few comments:

Would it be useful to add an optional user-defined tag to each expectation, and print the list of unmatched expectations along with their tags?

This could be marginally useful, yes. But what I think would be more useful would be printing the arguments that were passed that did not match. This does not depend on the predicates, but it would obviously require each argument to implement Debug. Is there a way to do it in such a way that Debug is not required, but if an argument implements it we could print the argument(s)?

I could imagine some theoretical way to introduce macros into this system which would help with stringifying, but that would probably end up being a big mess without much benefit.

mattklein123 commented 1 year ago

Ok I see that when you said MaybeDebugger you probably meant MaybeDebug and now I see why nightly is needed. I guess this is the same problem for printing args also. Gmock has that issue as well but C++ template specializations deal with that part so it's really the same underlying issue in rust. We need the specializations or something awful like some variant of autmock which requires args to be debug so it can print them.

mattklein123 commented 1 year ago

Sorry I see that you have your own variant of https://docs.rs/maybe-debug/latest/maybe_debug/ called MaybeDebugger :)

asomers commented 1 year ago

Oh, I didn't even know about the maybe_debug crate. Mockall's MaybeDebugger is actually older, being released in version 0.9.1 on 13-feb-2021. As for printing arguments that didn't match, that still wouldn't help if the user uses withf. And in my experience, withf is more common than with. So I don't think it would be a super worthwhile feature. Do you have any other ideas?

mattklein123 commented 1 year ago

As for printing arguments that didn't match, that still wouldn't help if the user uses withf. And in my experience, withf is more common than with. So I don't think it would be a super worthwhile feature. Do you have any other ideas?

Printing the arguments that come into the mock that don't match, irrespective of the matchers, is still very useful IMO. Sometimes I know what mock expectation is failing, but I don't know what arguments were passed, and then I have to go into the code and basically do a debug print to figure it out. So to me I would actually break this into 2 feature requests:

For the former, I understand the issue with functions, though I think that if specialization becomes standardized using something like MaybeDebug to allow things like eq() to be printed would still be quite useful. As I mentioned previously, I could envision some truly awful way of using a macro to write the expectation in such a way that it gets stringified and passed into the expectation framework to be printed later. This is very loosely formed and I would need to play around with this. It would also be messy so not sure it's worth it. Perhaps in the future MaybeDebug for predicates where it applies and your idea of the tag would be good enough.

For the latter, I think it really comes down to printing the incoming args, and that requires either requiring Debug or waiting for MaybeDebug to be in stable. The other idea just to write it here more plainly is to have some automock attribute like automock(require_debug_args = true). In this case, if the user agrees to force all mock function args to implement debug, we could then print them. This sounds like a lot of work also vs. waiting for MaybeDebug.

So, I agree we are probably stuck, but I will keep thinking about it. Thank you for the great chat!

asomers commented 1 year ago

Incoming arguments are already printed, if you use the nightly feature. Do you ? I do most of my development on the nightly toolchain anyway, so it's easy for me to flip that feature on. You can even do it in a build.rs script.

mattklein123 commented 1 year ago

Incoming arguments are already printed, if you use the nightly feature. Do you ? I do most of my development on the nightly toolchain anyway, so it's easy for me to flip that feature on. You can even do it in a build.rs script.

I don't currently but I guess I could consider this. But good to know that it's already implemented if that feature gets stabilized at some point.

asomers commented 1 year ago

I'm going to close this, on the assumption that I've answered your question.