The quickest way to take your unit testing to the next level is with a mocking framework. Let’s see how they work together.
It’s time to take your unit testing to the next level. You’ve implemented either NUnit or xUnit or MSTest in your projects. You’ve gotten your code coverage to 80+%. But there are just some things that are hard to test or validate in your project.
How do you test the “business logic” in your repository class? How do you test your dependent web service or database? Yeah, you can write special unit tests and create fake objects to mimic these dependencies, but why waste your time writing
code that does not ship with the end product. Or write a lot of code with the ExcludeFromCoverage
attribute. 😊 Well this is where mocking comes in.
Mocking is a process that allows you to create a mock object that can be used to simulate the behavior of a real object. You can use the mock object to verify that the real object was called with the expected parameters, and to verify that the real object was not called with unexpected parameters. You can also verify that the real object was called the expected number of times. And you can verify that the real object was called with the expected parameters and that the real object was not called with unexpected parameters. The possibilities are endless.
Mocking comes in three flavors: fakes, stubs and mocks. The fakes are the simplest. They are used when you want to test the behavior of a class that has no dependencies. The stubs are used when you want to test the behavior of a class that has dependencies and you do not want to change the behavior. The mocks are used when you want to test the behavior of a class that has dependencies and potentially modify behaviors.
For more information on mocking and the differences between stubs, fakes and mocks read the Fakes, Stubs and Mocks blog post.
First, you’ll need a mocking framework to get started as you don’t want to manage the life cycle of all mock objects. Something like Telerik JustMock or their free version JustMock Lite.
A mocking framework is what you use to create the objects that “pretends” to be the object(s) you are testing.
Now that you have a mocking framework, let’s get started with the primary parts of the unit testing process—Arrange, Act, Assert. Arrange, Act, Assert (or AAA) is a common term used to describe the process of setting up the test environment, executing the test, and verifying the results. It’s a best practice in unit testing. Basically, each of your unit tests should have these three parts:
When I write tests, in this case using xUnit, I generally start with this “stub” pattern:
[Fact]
public void GetContact_WithAnInvalidId_ShouldReturnNull()
{
// Arrange
// Act
// Assert
}
The method name follows a consistent format:
[Fact]
: This is a fact test.public void
: This is a public method.GetContact
: This is the method you are testing._WithAnInvalidId_
: Whatever variables you are using, in this example, an invalid id.ShouldReturnNull
: The expected outcome.While this convention is not required, I tend to use it so when I am looking at the results, or another engineer is looking at the code, they can see the intent of the test.
There are a lot of different types of things to mock, like services, databases, queues and other types of dependencies. For this introductory example, I am going to demonstrate different ways to test a class that requires a dependency of a database. I’ll be using xUnit and Telerik JustMock for these examples.
The project used in this example can be found here. This project is a C# project that was built on my Twitch stream. The application provides a way to manage a list of contacts. It uses a variety of technologies including:
With all these dependencies, I needed a way to validate that these dependencies are working as expected. And before you ask, no, I am not testing the functionality of SQL Server or Azure Storage or Azure Functions. I am only testing the interaction
with this service. That’s where mocking comes in. For the rest of this post, I’ll focus on testing the ContactManager
class and mocking the ContactRepository
.
Before we get started, let’s take a look at the ContactManager
class. The ContactManager
implements the IContactManager
interface. This is what we are testing.
public interface IContactManager
{
Contact GetContact(int contactId);
List<Contact> GetContacts();
List<Contact> GetContacts(string firstName, string lastName);
Contact SaveContact(Contact contact);
bool DeleteContact(int contactId);
bool DeleteContact(Contact contact);
List<Phone> GetContactPhones(int contactId);
Phone GetContactPhone(int contactId, int phoneId);
List<Address> GetContactAddresses(int contactId);
Address GetContactAddress(int contactId, int addressId);
/// Other methods removed for brevity
}
Full code for this class can be found here.
We’ll be mocking the ContactRepository
object which implements the IContactRepository
interface.
public interface IContactRepository
{
Contact GetContact(int contactId);
List<Contact> GetContacts();
List<Contact> GetContacts(string firstName, string lastName);
Contact SaveContact(Contact contact);
bool DeleteContact(int contactId);
bool DeleteContact(Contact contact);
List<Phone> GetContactPhones(int contactId);
Phone GetContactPhone(int contactId, int phoneId);
List<Address> GetContactAddresses(int contactId);
Address GetContactAddress(int contactId, int addressId);
/// Other methods removed for brevity
}
While objects that are being mocked don’t need to be interfaces, it certainly helps. The IContactManager
interface is a contract that defines the methods that interact with a contact. The ContactManager
class
implements the IContactManager
interface, in this case. However, one thing to note is that the ContactManager
requires an IContactRepository
dependency, which is what we are going to mock.
The IContactRepository
interface defines the contract with the database, which we do not want to test. This is where mocking comes in. We want to be able to test that the logic in the ContactManager
class is working as expected
without going back and forth with the database. This allows us to test things like validation or objects on save, returning the correct exceptions when things go wrong, etc.
Let’s start with the most common test. Let’s validate that a call to GetContacts
returns a list of contacts. We’ll start with the simplest test, and then move to more complex tests.
The signature of GetContacts
is:
Task<List<Contact>> GetContacts();
If we start with our template from above, we should stub out a test that looks like this:
public void GetContacts_ShouldReturnLists()
{
// Arrange
// Act
// Assert
}
Now, let’s look at the arrange part. For the arrange part, we need to setup the mocks so that the mocking framework knows what to mimic or mock. Here’s the arrange part for the GetContacts_ShouldReturnLists
method:
var mockContactRepository = Mock.Create<IContactRepository>();
Mock.Arrange(() => mockContactRepository.GetContacts())
.Returns(new List<Contact>
{
new Contact { ContactId = 1 }, new Contact { ContactId = 2 }
});
var contactManager = new ContactManager(mockContactRepository);
On line 1, we create a variable, mockContactRepository
, that is the mock of the IContactRepository
interface. Line 2, we create a mock of the GetContacts
method. Lines 3-6, we create a list of contacts and
tell the mock framework to return this object when a call is made to GetObjects
. Finally, on line 7, we create a new ContactManager
object and pass in the mock IContactRepository
object.
In this case, the act is trivial. We just call the GetContacts
method on the ContactManager
object.
var contacts = contactManager.GetContacts();
This should return a list of contacts with two contacts with the ids of 1 and 2.
Let’s validate that the list of contacts has two contacts.
Assert.NotNull(contacts);
Assert.Equal(2, contacts.Count);
Line 1 is checking that the list of contacts is not null. Line 2 is checking that the list of contacts has two contacts.
[Fact]
public void GetContacts_ShouldReturnLists()
{
// Arrange
var mockContactRepository = Mock.Create<IContactRepository>();
Mock.Arrange(() => mockContactRepository.GetContacts())
.Returns(new List<Contact>
{
new Contact { ContactId = 1 }, new Contact { ContactId = 2 }
});
var contactManager = new ContactManager(mockContactRepository);
// Act
var contacts = contactManager.GetContacts();
// Assert
Assert.NotNull(contacts);
Assert.Equal(2, contacts.Count);
}
There is a method in the ContactManager
called GetContact
that requires an integer as a parameter. In our business case, the identifier of a contact is a positive number (integer). So let’s set up some test that
makes sure a call to get GetContact
with a negative number returns null
and a call to get GetContact
with a positive number returns a contact.
For this, we’ll use a feature called matchers. Matchers let you ignore passing actual values as arguments used in mocks. Instead, they give you the possibility to pass just an expression that satisfies the argument type or the expected
value range. This means that we don’t have to write a test for each possible value. We can just write a test for the range of values. We are going to use the InRange
matcher for our two tests.
For the test, GetContact_WithAnInvalidId_ShouldReturnNull
where we expect a null
return, we would arrange the test like this:
Mock.Arrange(() =>
mockContactRepository.GetContact(Arg.IsInRange(int.MinValue, 0, RangeKind.Inclusive)))
.Returns<Contact>(null);
In this arrangement, we are saying that when a call to GetContact
is made with an argument that is in the range of int.MinValue
to 0, inclusive, we should return null
.
Our act and assert look like:
// Act
var contact = contactManager.GetContact(-1); // Any number less than zero
// Assert
Assert.Null(contact);
For the test, GetContact_WithAnValidId_ShouldReturnContact
, we would arrange the test like this:
Mock.Arrange(() =>
mockContactRepository.GetContact(Arg.IsInRange(1, int.MaxValue, RangeKind.Inclusive)))
.Returns(
(int contactId) => new Contact
{
ContactId = contactId
});
var contactManager = new ContactManager(mockContactRepository);
const int requestedContactId = 1;
This one required a little bit more work because we needed to specify an object to return, lines 3 to 6, and a value for the contact id, line 9, to validate in our test.
Our act and assert look like:
// Act
// Assumes that a contact record exists with the ContactId of 1
var contact = contactManager.GetContact(requestedContactId);
// Assert
Assert.NotNull(contact);
Assert.Equal(requestedContactId, contact.ContactId);
The GetContacts
method has an overload which expects two string parameters, one for first name and the other for last name. The method also requires that the first name and last name are not null
or empty. If so, it should
throw an ArgumentNullException
. Let’s create a test that validates that a call to GetContacts
with an empty first name and last name throws the exception.
Let’s arrange the test like this:
// Arrange
var mockContactRepository = Mock.Create<IContactRepository>();
Mock.Arrange(() =>
mockContactRepository.GetContacts(null, Arg.IsAny<string>()));
var contactManager = new ContactManager(mockContactRepository);
Here we are passing a null
for the FirstName
parameter and using the Arg.IsAny<string>
matcher for the LastName
parameter which will match any string.
Our act, which is also an assert, looks like this:
// Act
ArgumentNullException ex =
Assert.Throws<ArgumentNullException>(() => contactManager.GetContacts(null, "Guadagno"));
Here we are creating a variable ex
which is of type ArgumentNullException
and then we are asserting that the GetContacts
method throws an ArgumentNullException
when called with the FirstName
parameter set to null
and the LastName
parameter set to Guadagno
.
Then in the assert, we are checking that the exception message is correct.
// Assert
Assert.Equal("firstName", ex.ParamName);
Assert.Equal("FirstName is a required field (Parameter 'firstName')", ex.Message);
Note, JustMock supports an alternative way of testing that an exception is thrown. We can use Mock.Arrange
when setting up the test to throw the exception. We can use the Throws<T>
matcher to assert that an exception
of a specific type is thrown.
Mock.Arrange(() => contactManager.GetContacts(null, "Guadagno"))
.Throws<ArgumentNullException>("FirstName is a required field (Parameter 'firstName')");
The complete code for the ContactManager
class can be found here.
The complete code for the ContactManagerTest
class can be found here.
This just scratches the surface of mocking. There are many more ways to mock using a mocking framework like JustMock. Maybe we’ll cover more in a future post.
JustMock is available for free trial so you can see it in action. It’s also included as part of the robust Telerik DevCraft bundle.
Joe Guadagno is a Director of Engineering at Rocket Mortgage, the US’s largest mortgage lender, based in Detroit, Michigan. He has been writing software for over 20 years, has been an active member of the .NET community, serving as a Microsoft MVP in .NET for more than 10 years. At Rocket Mortgage, he leads three software development teams building and modernizing internal services. He has spoken throughout the United States and at international events on topics ranging from Microsoft .NET, Microsoft Azure, Ionic, Bootstrap and many others (see the complete list). When not sitting at a computer, Joe loves to hang out with his family and play games, as well as checking out the latest in Home Automation. You can connect with Joe on Twitter at @jguadagno, Facebook at JosephGuadagnoNet, and on his blog at https://www.josephguadagno.net.