-
-
Notifications
You must be signed in to change notification settings - Fork 812
Fix recursive verification #1093
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
Comments
Trying to get a better understanding on your proposal, especially this point
and assuming that for your example the following interfaces are used interface IParent {
IChild Child { get; }
}
interface IChild {
string Property { get; }
} and the test then instead does the following var parentMock = new Mock<IParent>();
var childMock = new Mock<IChild>();
childMock.Setup(c => c.Property).Returns("some value").Verifiable();
parentMock.Setup(p => p.Child).Returns(childMock.Object);
parentMock.Verify(); Would this also do the recursive verfication on childMock? Would the answer change if this change is made on the obove test? -parentMock.Setup(p => p.Child).Returns(childMock.Object);
+parentMock.Setup(p => p.Child).Returns(childMock.Object).Verifiable(); |
@siprbaum, thanks for asking!
No, this would no longer be the case.
No, this would make no difference. You'd have to do this:
The setup for In other words: (The choice of |
Your proposal makes much sense to me. I can only speak for myself, but I don't expect much breaking changes (if any) in our code base of over 15 000 tests. |
This is tricky indeed. I don't think I did a very good job of making things easily comprehensible from the beginning. Too much "magic" 😞 . I wonder if the "ChildMocks" collection for "recursive setups" would be a useful public API to have in the vNext SDK... With both that and public API to lookup the setups on a mock, you can verify precisely what you want, I think... |
@kzu, thanks for chiming in. I would love to hear how you originally intended (recursive) verification to work, or (if that differs) how you think it should operate now. Without having that knowledge, I would argue that |
TBH, I don't recall precisely what the original intended behavior. My intuition is that in general, recursive setups are "owned" by the mock you perform them on, where as "regular" setups aren't recursive. And so it seemed intuitive that for recursive setups, verifying the owning mock would also verify the child mocks. But for regular setups, that wouldn't typically be what you'd want (since that's likely why you created a separate Mock instance in the first place). |
Agreed 💯, a recursive setup like
The longer I think about it, the more I'm convinced that this should not happen. There shouldn't be any ownership or parent-child relation between mocks just because a setup on mock A happens to return (or act on) another mock B. I'm starting to think that if you want to verify several mocks, you should explicitly use a This makes it your choice exactly which mocks you want to verify (as opposed to Moq having to make a guess through assumed ownership relations that may not actually reflect your intention). |
Yeah, I see your point. Too many heuristics makes things harder to understand. I still haven't introduced the concept of the mock repository in v5, I was pondering whether it was necessary at all... You could trivially implement it yourself (or in a Contrib package) by just making your own factory methods that return them and keep track of them. With the rich introspection API in v5, it should be fairly trivial to implement the You can always just keep the variables around and verify yourself too, so... |
Oh yes, by all means, don't add I wanted to make sure that we are on the same page re: the semantics of recursive verification. It appears that we are, and that gives me some more confidence that "fixing" recursive verification in Moq 4 is the right thing to do, despite it being a breaking change. |
I wrote a comment on #858 before I discovered this ticket. I can re-post it here if it can help the discussion. |
@snake-scaly, there's no need to repost, the link to your post should suffice. Thanks for taking the time to add your two cents. It's reassuring to get another data point that's in agreement with the proposed change, because while I believe it would be a good & reasonable one, it'll also break things for some users of Moq... so I am still very hesitant to make it. Enough posts like yours will hopefully tip the scales and give me the courage to go ahead. Btw. I have had a few working drafts of an improved form of no-longer-very-recursive-at-all verification. It's been a while, but IIRC the biggest remaining uncertainty was with the intended semantics of |
Ok, I refreshed my memory on this thread, and I think I agree with the proposed design of having mocks own setups and not entire mocks. I don't think the scenarios that would break would be too common. I'd suggest testing the updated logic on the dotnet/aspnetcore repo which has a ton of tests and is a heavy Moq user and see if there's much breakage. If there isn't any (or very few), then perhaps it's fine? I there's much cry, worst case a new property/arg on verify could provide the legacy behavior? I was never much of a fan of |
Thanks for the suggestion of test-driving the changed verification behaviour using the Regarding (Btw. by now I am fairly certain that the new behavior for |
I wouldn't deprecate |
Due to lack of recent activity, this issue has been labeled as 'stale'. |
Due to lack of recent activity, this issue has been labeled as 'stale'. |
This issue will now be closed since it has been labeled 'stale' without activity for 30 days. |
Problem statement:
Every now and then, this repository sees reports about verification unexpectedly including setups or invocations from mocks other than the one on which
Verify
,VerifyAll
, orVerifyNoOtherCalls
was called. Recently, we got these reports:The reason for this happening is that verification is recursive, and visits all mocks safely reachable from the mock on which the verification call is made. I've come to summarise this recursion logic as "mocks own other mocks".
As the above issues (and probably others) demonstrate, this behavior isn't intuitive, and it often isn't desired. The recommended workaround to avoid it has been to replace calls of the form
setup.Returns(value)
withsetup.Returns(() => value)
. Using a callback instead of a fixed value will stop verification from continuing along that axis ifvalue
is itself a mock object.Proposal:
Perhaps the time has come to fix this problem with recursive verification. One way to do this would be to change the logic to something I'd summarize as "mocks own setups"; that is, recursive verification should focus on setups, not on mocks. It should not travel along edges from one mock to other mocks, but along edges from one mock to its setups.
(Edit: To be more specific what that means: a call to
mock.Verify[All]()
should verify exactly those setups that were made via amock.Setup[Get,Set,Add,Remove,Sequence](...)
call and nothing else. Further,mock.VerifyNoOtherCalls()
deals with invocations, not setups, and should therefore no longer be recursive at all.)Assumed benefits:
Here's a summary of the assumed advantages of this approach:
Mock
objects already have aSetups
collection (i.e. they already do "own" setups), whereas they do not have some kind ofChildMocks
collection..Verifiable()
.Some other remarks / changes:
VerifyNoOtherCalls
would no longer be "recursive" in any way.InnerMock
property on individualISetup
s (which is a very recent addition) could be deprecated. I noticed that documenting it, and its interaction with the recursive verification algorithm, was quite difficult, and that could be another sign that it's not intuitive enough.parentMock.Setup(p => p.Child.Property).Returns(...).Verifiable()
, the setup forProperty
would sit on the child mock, but it would also be included when you do aparentMock.Verify()
.Question to you: is it worth making this breaking change?
This would be a functional change that is pretty much guaranteed to break some existing code out there... I suspect that breakage wouldn't be too severe (it breaks 6 out of roughly 3000 test cases in Moq's own unit test suite, FWIW), but I have no real way of knowing. I think the change would be for the better, and make Moq more intuitive, but I'd love to hear @kzu's and other users' thoughts and opinions about this proposed change. How do you think (recursive) verification should work? What should get included during verification?
/cc @rleeson24, @matkoliptak, @killergege, @Zaeranos, @molszews, @penguintree (who opened / commented on the issues mentioned above)
The text was updated successfully, but these errors were encountered: