Link Search Menu Expand Document

C# Moq Unit Tests

Watch the video here

A unit test attempts to verify the functionality of a single unit of work. Given a class method with inputs and measurable outputs a unit test aims to verify the correctness of part of the method without peeking into private state management such as a database.

Prerequisites

Loose Agenda

Learn about the Moq unit testing framework in .NET Identify scenarios worth unit testing

Step by Step

Setup testing framework

Create a directory for today’s exercise and navigate to it in a terminal instance.

We’re going to leverage the code created in this exercise in order to bootstrap our effort. We will copy the src directory from that repository into today’s exercise’s directory.

From a terminal instance in the root directory, run dotnet new sln to create an empty solution.

Add the src.csproj to the solution by running dotnet sln add src/src.csproj

Create a new .NET unit test project in it’s own directory by running dotnet new mstest -o test

Add the test.csproj to the solution by running dotnet sln add test/test.csproj

Add a reference from the test project to the src project by running dotnet add test/test.csproj reference src/src.csproj

Add a reference to Moq to the test.csproj by running dotnet add test/test.csproj package moq

Now let’s open our solution in Visual Studio 2022 Preview.

Create a test without Moq

In Visual Studio, open test/UnitTest1.cs

Back in the C# Unit Tests exercise we talked about Arrange/Act/Assert. We’re going to leverage that practice in order to test the src/Core/ContactService class.

We want to make sure that our Create method is validating for a null input, so let’s rename the TestMethod1 method to ContactService_Create_Throws_InvalidOperationException_For_NullInput.

If the ContactService is working as intended then we should expect the code to throw an InvalidOperationException. As such, just above the TestMethod let’s add [ExpectedException(typeof(InvalidOperationException))].

The Arrange step will be creating our repository, service and input contact of null.

The Act step will be calling the service.Create method.

The Assert step is handled by our ExpectedException attribute. If an InvalidOperationException is not thrown then the test will fail.

Our method now looks like

[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void ContactService_Create_Throws_InvalidOperationException_For_NullInput()
{
    // Arrange
    var repository = new ContactRepository();
    var service = new ContactService(repository);
    Contact inputContact = null;

    // Act
    service.Create(inputContact);
}

Open View > Test Explorer or press Ctrl+E, T on the keyboard to open the Test Explorer window. Run all tests in this window and note that the test should succeed.

Create a test with Moq

Note that in the previous steps we created a test but required a dependency on the exact implementation details of infrastructure code (the ContactRepository.) If this repository was connected to a database then we would have potential to actually call the database. In the spirit of only testing a single unit of work, we want to replace the specific infrastructure implementation with a mocked implementation of the infrastructure contract.

We can adjust the method we just created with Moq by writing:

[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void ContactService_Create_Throws_InvalidOperationException_For_NullInput()
{
    // Arrange
    var repository = new Mock<IContactRepository>();
    var service = new ContactService(repository.Object);
    Contact inputContact = null;

    // Act
    service.Create(inputContact);
}

Note that we’ve removed the need for the using declaration using src.Infrastructure; and instead have using Moq;

Moq Setup

For the immediate our ContactService.Retrieve method just forwards the call to our Repository. Let’s create a new test method to verify that the result from the repository is returned through the service. In order to do this we will need to use Moq’s Setup method.

[TestMethod]
public void ContactService_Retrieve_Returns_Contact_From_Repository()
{
    // Arrange
    var repository = new Mock<IContactRepository>();
    var service = new ContactService(repository.Object);
    var contactName = "Pete";
    var expectedContact = new Contact()
    {
        Name = contactName,
        Number = "5031234567",
        Type = "Person"
    };
    repository.Setup(repo => repo.Retrieve(contactName)).Returns(expectedContact);

    // Act
    var actualContact = service.Retrieve(contactName);

    // Assert
    Assert.IsNotNull(actualContact);
    Assert.AreEqual(actualContact.Name, expectedContact.Name);
    Assert.AreEqual(actualContact.Number, expectedContact.Number);
    Assert.AreEqual(actualContact.Type, expectedContact.Type);
}

We can run this test to verify that each property received matches each property expected.

Note that we call repository.Setup(repo => repo.Retrieve(contactName)).Returns(expectedContact); in order to instruct our Mock object on how to behave if it receives a very specific name input contactName. If we don’t care about the specific input name we can instead use It.IsAny<string>() to instruct our Mock object to return the expectedContact regardless of input string. Note that if we changed our code to the following it would work as well

[TestMethod]
public void ContactService_Retrieve_Returns_Contact_From_Repository()
{
    // Arrange
    var repository = new Mock<IContactRepository>();
    var service = new ContactService(repository.Object);
    var expectedContact = new Contact()
    {
        Name = "Pete",
        Number = "5031234567",
        Type = "Person"
    };
    repository.Setup(repo => repo.Retrieve(It.IsAny<string>())).Returns(expectedContact);

    // Act
    var actualContact = service.Retrieve(expectedContact.Name);

    // Assert
    Assert.IsNotNull(actualContact);
    Assert.AreEqual(actualContact.Name, expectedContact.Name);
    Assert.AreEqual(actualContact.Number, expectedContact.Number);
    Assert.AreEqual(actualContact.Type, expectedContact.Type);
}

Verify a dependency call

In the above test we expect that the repository Retrieve method is being called and mock behavior accordingly. If we wanted to additionally check that this method is called as many times as we expect then we can do so in the Act section with Verify. Let’s change our ContactService_Retrieve_Returns_Contact_From_Repository code to the following.

[TestMethod]
public void ContactService_Retrieve_Returns_Contact_From_Repository()
{
    // Arrange
    var repository = new Mock<IContactRepository>();
    var service = new ContactService(repository.Object);
    var expectedContact = new Contact()
    {
        Name = "Pete",
        Number = "5031234567",
        Type = "Person"
    };
    repository.Setup(repo => repo.Retrieve(It.IsAny<string>())).Returns(expectedContact);

    // Act
    var actualContact = service.Retrieve(expectedContact.Name);

    // Assert
    Assert.IsNotNull(actualContact);
    Assert.AreEqual(actualContact.Name, expectedContact.Name);
    Assert.AreEqual(actualContact.Number, expectedContact.Number);
    Assert.AreEqual(actualContact.Type, expectedContact.Type);

    repository.Verify(repo => repo.Retrieve(It.IsAny<string>()), Times.Once);
}

Running this test now verifies that the Retrieve method is only called once. Note that if we change the Times.Once to Times.Exactly(3) then the test will fail.

Additional Resources