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

Always record calls to += and -= event accessors #1082

Merged
merged 3 commits into from
Oct 27, 2020

Conversation

stakx
Copy link
Contributor

@stakx stakx commented Oct 26, 2020

Resolves #1058.

@stakx stakx added this to the 4.15.0 milestone Oct 26, 2020
Copy link

@siprbaum siprbaum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate why CallBase makes such a difference for the event raising?
I looked around, e.g. the quick start guide but couldn't figure out why it makes a difference

tests/Moq.Tests/EventHandlersFixture.cs Show resolved Hide resolved
tests/Moq.Tests/EventHandlersFixture.cs Show resolved Hide resolved
tests/Moq.Tests/EventHandlersFixture.cs Show resolved Hide resolved
@stakx
Copy link
Contributor Author

stakx commented Oct 27, 2020

Could you elaborate why CallBase makes such a difference for the event raising?

I'll try, but keep in mind that I didn't implement that part of Moq and, like you, had to try and make sense of it; so it's possible that the following explanations aren't entirely accurate.

First, consider how one raises an event from outside the class that defines it. The short version is, it can't be done; not without reflecting over private members, whose names you can only guess. C#'s events are a convenient shortcut that generates a delegate-typed backing field, as well as two accessors for modifying that field. But the field isn't accessible outside the class.

That's why Moq builds a completely separate event handling mechanism for its mock.Raise(m => m.SomeEvent += null) method. It creates its own delegate-typed backing field for SomeEvent. When you invoke mock.Object.SomeEvent += ... or -=, the handlers are subscribed to that backing field, and Raise will be able to invoke those handlers.

Now you've got a problem, however. If you are mocking a class, and that class has some methods that raise SomeEvent, that will no longer trigger your subscribed handlers. So you might end up in situations where you can't properly test events / event handlers.

That's where CallBase comes it. It basically gives you the choice whether Moq should subscribe handlers to the actual event (i.e. delegate to the base class' +=, -= accessors, thus CallBase) or whether it should use its own mechanism. In the former case, you won't be able to use Raise, in the latter case, the base class won't see your event handlers. You need to decide between these two options based on what's desired / needed in your specific test situation.

@siprbaum
Copy link

Thank you for the explanation. That clear things up for me.
But now I wonder how one is supposed to know that without reading the explanation, e.g. how to get it somehow documented so anyone not reading that detailed explanation has a chance to understand the implications.

@stakx
Copy link
Contributor Author

stakx commented Oct 27, 2020

@siprbaum, that's a good question, and I don't have the answer.

Generally speaking, mocking classes tends to be more complicated than mocking interfaces because you have a base implementation to consider at all stages. For that reason, the first recommendation should be to write / refactor your code in such a way that you can work with (and mock) interfaces. That's generally helpful re: testability, and regarding mocked events, it makes things conceptually straightforward again (as you won't need nor have the choice whether to use CallBase or not).

But for those users mocking classes... after all these years, Moq still doesn't have proper documentation. I suspect the quickstart in the wiki was only ever meant to demonstrate the most common, straightforward usages; it probably isn't a good place for more in-depth looks at those aspects of Moq that aren't very intuitive.

That being said, the wiki can be edited by everyone. Feel free to cook up some documentation and add it as a new wiki page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Remove mandatory SetupAdd & SetupRemove for eventhandler subscription verification.
2 participants