The benefits of unit tests are well known. They test the logic of our code in isolation of our dependencies and typically a well-tested code base will most likely be more compliant with the SOLID principles of design.

Moq is great for mocking out your dependencies but its effectiveness in a set of tests depends on how relevant the assertions against those mocks are and we need to use it in a manner that remains understandable to our team whilst allowing the underlying flow of our code to execute without compromising what can be tested.

Shorthand Mock.Of<T>

For many situations, we don't want to go through the ceremony of

creating a mock setting up behavior accessing the underlying proxied object when passing the mock to our code

For these times Moq has a static Of method which takes a generic parameter that gives us something we can use directly in our tests.

So we can do:

IUser currentUser = Mock.Of<IUser>(u => u.Id == Guid.NewGuid());

instead of

Mock<IUser> userMock = new Mock<IUser>(); //many lines later userMock.SetupGet(u => u.Id).Returns(Guid.NewGuid); //even more lines later var result = service.GetApplicantUsingUserId(userMock.Object);

We can even use this to setup multiple properties:

IUser _currentUser = Mock.Of<IUser>(u => u.Id == Guid.NewGuid() && u.branchId == 1);

This one can help make tests easier to follow whilst also reducing the effort involved in writing the tests.

Calling through to a fake implementation

Much of the time when we are setting the behavior of a mock we specify a value to return when a method on our mock is called. However, there are times where our code may want to:

return the value that was passed to the method instead of returning a new object or value. update a value passed to our method and then return something e.g the primary key on creation of a record or do something else that requires logic before returning the expected value.

Consider the code below:

_accountsRepo.Setup(r => r.Save(It.IsAny<Account>())).Returns<Account>( a => { a.Id = newAccountId; return a; }); //many lines later _newAccountNotificationService.Verify(s => s.Notify(newAccountId, userId));

We are setting up our _accountsRepo object to have a value assigned to it's id property before returning it. Later we are verifying that our _newAccountNotificationService was called with this id . If our returned value is assigned for the first time in our code upon the call to the Save method, this is not necessary.

Consider:

public bool CreateAccount(Account newAccount) { newAccount = _accountsRepo.Save(newAccount); //more lines of code _newAccountNotificationService.Notify(newAccount.Id, userId); }

The return value is assigned to an existing variable so we need something like this to test the flow of our code. In an ideal world, our code may be structured differently which may make it easier to test than the scenario above. However, it is not uncommon for testing to start after the code has gone into production. In such cases, scenarios like this are very common.

This allows us to write our fake implementation in the body of our test which can make tests easier to follow. If our classes are structured in a manner that means dependencies are always referenced by an interface, then we can also use a fake implementation of our interface rather than using Moq for this. But in many situations, it can be more readable and maintainable to use Moq to declare our fake implementation in our tests.

Verifying Properties on Reference types

So most examples that show how to setup or verify behavior use something like

repository.Setup(r => r.GetById(It.IsAny<int>())).Returns(record)

If we want to constrain this to a specific value we can do:

repository.Setup(r => r.GetById(recordId)).Returns(record)

Now it is good practice to use objects as parameters to methods instead of having many (3 or more) parameters that are all value types (assuming that they are all related in a logical manner), but this makes it harder to constrain values within an object.

Sadly this can result in a lot of tests reverting to using It.IsAny<T> which can badly affect the usefulness of the test.

Fortunately, Moq has the less frequently used It.Is<T> to allow for more specific matching on setup and verification of mocks.

So we can do:

_accountRepo.Verify(r => r.Save(It.Is<Account>(a => a.Type == Type.Savings)));

To verify that our _accountRepo variable's method Save was called with an Account object that has a Type property that matches the Type.Savings value(enum).

This would provide far more value than using the Is.Any<T> method which is far less precise, as it would verify our basic code-flow but not the underlying results of its execution.

Recording invocation conditionally with Callback

Sometimes, we want to record how many times a method on a mock was called. Moq provides a callback function that allows us to run code that executes every time a method on our mocks is called.

var calls = 0; mock.Setup(accountService=> accountService.Notify(It.IsAny<AccountType>)) .Returns(true) .Callback(() => calls++);

NB: Callback can be called before or after we setup our return value.

Now, this example is somewhat contrived and there are better ways of doing this. However, the callback method becomes very useful when we are wanting to read what the mock method was called with before executing our callback.

Consider:

var calls = 0; mock.Setup(accountService=> accountService.Notify(It.IsAny<AccountType>())) .Returns(true) .Callback<AccountType>((t) => { if(t == AccountType.Savings) calls++ });

We are now counting every time that our Notify method was called with the a value of AccountType.Savings . This one can be really useful for dealing with code that occurs in a loop or other hard to reach/test scenarios, but must be treated with an element of caution. Generally speaking, we want very basic logic in both our test setup and execution so if more than one statement is used in the callback then I'd question whether code that is being tested is structure correctly.

Final thoughts

Moq can be a really powerful tool to improve the ease of testing and quickly increase the quality of our test assertions. With just a small grasp of its available functionality we are able to provide tremendous value to the quality and readability of our tests.