Reputation: 359
Alternate title: What do these expressions mean?
EDIT: This post almost helped, but just explained the difference between the two setup functions, not how to read them.
I'm learning how to use moq and putting together a basics training for my team, and after going through this video and looking up other stackoverflow posts (among others), I think I understand it well enough to use it, but only in the sense of "this is just the way it's done". I have no justification or ability to explain the syntax. Please help.
For the record, I understand the use of C# templating and expressions and functions and actions and events alone, but jamming them together? The meta is real.
Consider the following code snippet from the one of the video demonstration's unit tests, Should_Mock_Events_Based_On_Action()
(unit test source, IRepo source):
var mockRepo = new Mock<IRepo>();
mockRepo.Setup(x => x.AddRecord(null))
.Raises(m => m.FailedDatabaseRequest += null, this, EventArgs.Empty);
The Setup(...)
function seems to read, "Set up a function that takes an IRepo
and then calls AddRecord(...)
with null
." But this setup is mocking up IRepo.AddRecord(...)
, which is not an expression. Somehow this gets translated inside mock to "Set up a function for IRepo
called AddRecord(...)
such that, when it takes null
, it behaves a certain way." But it doesn't read that way. How is this supposed to read? Somehow the expression gets turned into an actual function call somewhere, somehow.
The Raises(...)
function really confuses me. It looks like it is saying, "The previously set up function raises an event that takes an IRepo
and adds null
to its event handler." And...somehow this makes the event happen? That += null
operation doesn't return anything, and I haven't been able to figure out how this gets understood as the event that this handler is looking for. I've found a number of articles and SO posts that show that this is just how it's done, but none of them (that I've found yet) explain why. How is this supposed to read?
Upvotes: 0
Views: 270
Reputation: 359
Answering own question.
My confusion was rooted in not understanding how C#'s expressions worked (not lambda expressions, but specifically Expression<>
). They are an exposure of compiler theory (specifically "expression trees") and can be used to deconstruct lambdas into pieces, including parameters, constants, binary operators, and others. Moq takes advantage of this and uses the pieces to construct its own object with the specified functionality + support code for verification. I didn't know C# could do this.
Tutorial series that alleviated my confusion: C# Expression Trees
Upvotes: 0
Reputation: 3103
Say I have an interface ITest
that has a single method:
public interface ITest
{
bool IsEvent(int input);
}
I then want to mock this - and remember, right now I have no actual concrete class.
var mock = new Mock<ITest>();
Now I want to set up 2 calls:
mock.Setup(x => x.IsEven(1)).Returns(false);
mock.Setup(x => x.IsEven(2)).Returns(true);
This is saying to the mock object:
If your method IsEven gets called with a value of 1 then return false.
If your method IsEven gets called with a value of 2 then return true.
You are setting up behaviour on your mocked object.
So if I then do this in code:
var for1 = mock.Object.IsEven(1);
var for2 = mock.Object.IsEven(2);
Variable for1
will be false and for2
will be true because I told the mock object that was what it should do. The parameter for Setup
effectively says "This is the behaviour I want you to look out for and I will then tell you what to do". In my case, I use the Returns
method to specify what actually gets returned from my mock object in that specific case.
In your specific case:
mockRepo.Setup(x => x.AddRecord(null))
.Raises(m => m.FailedDatabaseRequest += null, this, EventArgs.Empty);
This is saying to the mocked object
If someone calls the AddRecord method with a parameter of null then raise an event of type FailedDatabaseRequest
For more information on the Raises
method, have a look at the Moq quickstart documentation for events here.
For a more in depth view of raising events with Moq there is some useful information here - specifically it talks about the += null
that is causing you some confusion:
To raise an event from a mock object we use its Raise method. This accepts two parameters. The first is a lambda expression that includes an empty event subscriber for the event to raise. Although not the most elegant syntax, this is required to allow Moq to understand how the event is used. The second parameter provides the event arguments that will be included with the event.
Upvotes: 2