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
Add ReturnValue to IInvocation #921
Conversation
Thanks for the PR. I've been thinking about doing the same, too, but then didn't because I didn't have a clean solution for the case where an invocation throws an exception (i.e. doesn't return anything). It strikes me as wrong that one should be able to query Do you have a suggestion for that case? |
As far as I can see, any exception raised is not yet recorded in the Add an
|
A fourth option would be to keep the interface as is and return either the returned value or the exception when |
Thanks for these four options. FWIW, Moq v5 appears to have gone with a combination of the 1st and 3rd option. A fifth option I have been considering is this one: partial interface IInvocation
{
bool TryGetReturnValue(out object returnValue);
bool TryGetReturnValue(out object returnValue, out Exception exception);
} What I like about a The second overload might have to wait until Moq is actually recording thrown exceptions, too. (What's your use case? Would you want to know about thrown exceptions, if that information were also available in the records?) Also, I'm not sure the semantics of two The first overload would be quite neat (IMHO) but we'd actually have to add some kind of flag to |
What I like about
In the first case, it is ambiguous whether The second option is indicating that the method returned null, but it actually returned nothing. The third option is semantically more correct but technically wrong and is hard to discover (needs documentation). The fourth option violates the "Try" pattern. Besides this problem, I don't like how you would use the TryGet methods: if (invocation.TryGetReturnValue(out var returnValue, out var exception)
{
// Was the invocation successfull?
if (exception == null) // Checking returnValue != null would be a bug!
{
// Now you also need to make sure that the returnValue is really what the method returned:
if (returnValue != new System.Void()) // or alternatively
if (invocation.Method.ReturnType != typeof(System.Void))
{
// returnValue is really the returned value.
} else {
// the method returned nothing.
}
} else {
// the method raised an exception
}
} else {
// TryGetReturnValue(returnValue, exception) returned false. What does this mean?
} My use case is fairly simple. I just need the return value of a particular But I could imagine other use cases where you would query the |
I'd go with your option (4). Throwing
My interpretation of this is that yes, a
I feared that the semantics with two out parameters aren't immediately obvious (but could be easily documented). That's what I had in mind: Debug.Assert(invocation.Method.ReturnType != typeof(void));
if (invocation.TryGetReturnValue(out var returnValue, out var exception))
{
Debug.Assert(exception == null);
// do something with `returnValue`
}
else
{
Debug.Assert(exception != null);
// do something with `exception`
} |
Throwing an exception is bad when a user just wants to query the return values of all invocations of a mock. The user would be forced to filter the invocation list before. This filter is hard to discover: That And another thing: I just read that So everything related to RetrunValue only makes sense in the latter context. How do you want to solve that problem? |
you say that an exception would be "bad" in that case. I disagree. Without the exception, you're silently going to assume that a
I agree that it's not as easy as other parts of Moq's API... but does it really have to be easy? I would expect that the majority of users won't ever need to look at
Good point. My proposal would still work reliably for the I could argue for common sense (why would you query the return value in a So we're at a point where we could implement How about...
(As before, I think we could wait with the exception property / method until it's actually needed.) |
I'm fine with the exception approach. I just wanted to point out use cases where this behavior makes things more difficult (but also forces the user to do things right).
In my opinion, throwing a I like the simple properties you proposed in 2. with the following semantics:
Implementing this sematic might not be possible without tracking exceptions. What do you think? |
I don't think we'd have to be quite that strict: IIRC it should be easily possible to track whether or not an invocation has returned. If it has returned, there's no strict reason to unconditionally throw. That being said, my gut feeling tells me that having properties that throw for a variety of reasons might be somewhat undesirable. I'm getting the impression one will have to tiptoe around those properties very carefully in order to not provoke an exception. For example:
The latter (throwing) case implies that one will have to always query If we get to that point, we could just as well use the Try pattern, which provides some additional safety and guidance. I think that if we do go with properties, they probably shouldn't throw at all... you simply get the data that's available, and it'll be up to you to interpret the result (e.g. the meaning of All things considered, my vote tends to be for a |
Hi @MaStr11, it's been a while. After having worked on getting Please give me some more time to take another close look at how Moq 5 does this. |
@MaStr11, sorry once again for the long silence. I've mostly let Moq rest for a bit during the summer and am only now getting back to work on it. I'd be happy to merge your PR! If you're still interested, could you please rebase this to current ## Unreleased
#### Added
* New method overloads for `It.Is`, `It.IsIn`, and `It.IsNotIn` that compare values using a custom `IEqualityComparer<T>` (@weitzhandler, #1064)
Implement It.Is, It.IsIn, It.IsNotIn with a comparer overload (#1059)
+* New property `IInvocation.ReturnValue` to query recorded invocations' return values (@MaStr11, #921)
#### Fixed |
Done. Thanks for accepting the change. |
@MaStr11 – done. 🚀 Thanks for your contribution, and also for your patience; I know that I have been super-slow on this one. |
P.S. @MaStr11: In case you were wondering where we stand regarding thrown exceptions, I didn't forget about it, but thought it would be cleaner to do that in a separate PR, to keep this one as straightforward as it was. If you would like to submit a PR that adds an It shouldn't be too hard to do, setting the |
Bumps [Moq](https://github.com/moq/moq4) from 4.14.7 to 4.15.2. #Changelog *Sourced from [Moq's changelog](https://github.com/moq/moq4/blob/master/CHANGELOG.md).* > ## 4.15.2 (2020-11-26) > > #### Changed > > * Upgraded `System.Threading.Tasks.Extensions` dependency to version 4.5.4 (@JeffAshton, [#1108](devlooped/moq#1108)) > > > ## 4.15.1 (2020-11-10) > > #### Added > > * New method overloads for `It.Is`, `It.IsIn`, and `It.IsNotIn` that compare values using a custom `IEqualityComparer<T>` (@weitzhandler, [#1064](devlooped/moq#1064)) > * New properties `ReturnValue` and `Exception` on `IInvocation` to query recorded invocations return values or exceptions (@MaStr11, [#921](devlooped/moq#921), [#1077](devlooped/moq#1077)) > * Support for "nested" type matchers, i.e. type matchers that appear as part of a composite type (such as `It.IsAnyType[]` or `Func<It.IsAnyType, bool>`). Argument match expressions like `It.IsAny<Func<It.IsAnyType, bool>>()` should now work as expected, whereas they previously didn't. In this particular example, you should no longer need a workaround like `(Func<It.IsAnyType, bool>)It.IsAny<object>()` as originally suggested in [#918](devlooped/moq#918). (@stakx, [#1092](devlooped/moq#1092)) > > #### Changed > > * Event accessor calls (`+=` and `-=`) now get consistently recorded in `Mock.Invocations`. This previously wasn't the case for backwards compatibility with `VerifyNoOtherCalls` (which got implemented before it was possible to check them using `Verify{Add,Remove}`). You now need to explicitly verify expected calls to event accessors prior to `VerifyNoOtherCalls`. Verification of `+=` and `-=` now works regardless of whether or not you set those up (which makes it consistent with how verification usually works). (@80O, @stakx, [#1058](devlooped/moq#1058), [#1084](devlooped/moq#1084)) > * Portable PDB (debugging symbols) are now embedded in the main library instead of being published as a separate NuGet symbols package (`.snupkg) (@kzu, [#1098](devlooped/moq#1098)) > > #### Fixed > > * `SetupProperty` fails if property getter and setter are not both defined in mocked type (@stakx, [#1017](devlooped/moq#1017)) > * Expression tree argument not matched when it contains a captured variable – evaluate all captures to their current values when comparing two expression trees (@QTom01, [#1054](devlooped/moq#1054)) > * Failure when parameterized `Mock.Of<>` is used in query comprehension `from` clause (@stakx, [#982](devlooped/moq#982)) > > > ## 4.15.0 > > This version was accidentally published as 4.15.1 due to an intermittent problem with NuGet publishing. #Commits - [`f2aa090`](devlooped/moq@f2aa090) ...
Fixes #920