Iteration #5 – Create unit tests (C#)

by Microsoft

Download Code

In the fifth iteration, we make our application easier to maintain and modify by adding unit tests. We mock our data model classes and build unit tests for our controllers and validation logic.

Building a Contact Management ASP.NET MVC Application (C#)

In this series of tutorials, we build an entire Contact Management application from start to finish. The Contact Manager application enables you to store contact information - names, phone numbers and email addresses - for a list of people.

We build the application over multiple iterations. With each iteration, we gradually improve the application. The goal of this multiple iteration approach is to enable you to understand the reason for each change.

  • Iteration #1 - Create the application. In the first iteration, we create the Contact Manager in the simplest way possible. We add support for basic database operations: Create, Read, Update, and Delete (CRUD).

  • Iteration #2 - Make the application look nice. In this iteration, we improve the appearance of the application by modifying the default ASP.NET MVC view master page and cascading style sheet.

  • Iteration #3 - Add form validation. In the third iteration, we add basic form validation. We prevent people from submitting a form without completing required form fields. We also validate email addresses and phone numbers.

  • Iteration #4 - Make the application loosely coupled. In this fourth iteration, we take advantage of several software design patterns to make it easier to maintain and modify the Contact Manager application. For example, we refactor our application to use the Repository pattern and the Dependency Injection pattern.

  • Iteration #5 - Create unit tests. In the fifth iteration, we make our application easier to maintain and modify by adding unit tests. We mock our data model classes and build unit tests for our controllers and validation logic.

  • Iteration #6 - Use test-driven development. In this sixth iteration, we add new functionality to our application by writing unit tests first and writing code against the unit tests. In this iteration, we add contact groups.

  • Iteration #7 - Add Ajax functionality. In the seventh iteration, we improve the responsiveness and performance of our application by adding support for Ajax.

This Iteration

In the previous iteration of the Contact Manager application, we refactored the application to be more loosely coupled. We separated the application into distinct controller, service, and repository layers. Each layer interacts with the layer beneath it through interfaces.

We refactored the application to make the application easier to maintain and modify. For example, if we need to use a new data access technology, we can simply change the repository layer without touching the controller or service layer. By making the Contact Manager loosely coupled, we ve made the application more resilient to change.

But, what happens when we need to add a new feature to the Contact Manager application? Or, what happens when we fix a bug? A sad, but well proven, truth of writing code is that whenever you touch code you create the risk of introducing new bugs.

For example, one fine day, your manager might ask you to add a new feature to the Contact Manager. She wants you to add support for Contact Groups. She wants you to enable users to organize their contacts into groups such as Friends, Business, and so on.

In order to implement this new feature, you'll need to modify all three layers of the Contact Manager application. You'll need to add new functionality to the controllers, the service layer, and the repository. As soon as you start modifying code, you risk breaking functionality that worked before.

Refactoring our application into separate layers, as we did in the previous iteration, was a good thing. It was a good thing because it enables us to make changes to entire layers without touching the rest of the application. However, if you want to make the code within a layer easier to maintain and modify, you need to create unit tests for the code.

You use a unit test to test an individual unit of code. These units of code are smaller than entire application layers. Typically, you use a unit test to verify whether a particular method in your code behaves in the way that you expect. For example, you would create a unit test for the CreateContact() method exposed by the ContactManagerService class.

The unit tests for an application work just like a safety net. Whenever you modify code in an application, you can run a set of unit tests to check whether the modification breaks existing functionality. Unit tests make your code safe to modify. Unit tests make all of the code in your application more resilient to change.

In this iteration, we add unit tests to our Contact Manager application. That way, in the next iteration, we can add Contact Groups to our application without worrying about breaking existing functionality.

Note

There are a variety of unit testing frameworks including NUnit, xUnit.net, and MbUnit. In this tutorial, we use the unit testing framework included with Visual Studio. However, you could just as easily use one of these alternative frameworks.

What Gets Tested

In the perfect world, all of your code would be covered by unit tests. In the perfect world, you would have the perfect safety net. You would be able to modify any line of code in your application and know instantly, by executing your unit tests, whether the change broke existing functionality.

However, we don t live in a perfect world. In practice, when writing unit tests, you concentrate on writing tests for your business logic (for example, validation logic). In particular, you do not write unit tests for your data access logic or your view logic.

To be useful, unit tests must execute very quickly. You easily can accumulate hundreds (or even thousands) of unit tests for an application. If the unit tests take a long time to run then you'll avoid executing them. In other words, long running unit tests are useless for day to day coding purposes.

For this reason, you typically do not write unit tests for code that interacts with a database. Running hundreds of unit tests against a live database would be too slow. Instead, you mock your database and write code that interacts with the mock database (we discuss mocking a database below).

For a similar reason, you typically do not write unit tests for views. In order to test a view, you must spin up a web server. Because spinning up a web server is a relatively slow process, creating unit tests for your views is not recommended.

If your view contains complicated logic then you should consider moving the logic into Helper methods. You can write unit tests for Helper methods that execute without spinning up a web server.

Note

While writing tests for data access logic or view logic is not a good idea when writing unit tests, these tests can be very valuable when building functional or integration tests.

Note

ASP.NET MVC is the Web Forms View Engine. While the Web Forms View Engine is dependent on a web server, other view engines might not be.

Using a Mock Object Framework

When building unit tests, you almost always need to take advantage of a Mock Object framework. A Mock Object framework enables you to create mocks and stubs for the classes in your application.

For example, you can use a Mock Object framework to generate a mock version of your repository class. That way, you can use the mock repository class instead of the real repository class in your unit tests. Using the mock repository enables you to avoid executing database code when executing a unit test.

Visual Studio does not include a Mock Object framework. However, there are several commercial and open source Mock Object frameworks available for the .NET framework:

  1. Moq - This framework is available under the open source BSD license. You can download Moq from https://code.google.com/p/moq/.
  2. Rhino Mocks - This framework is available under the open source BSD license. You can download Rhino Mocks from http://ayende.com/projects/rhino-mocks.aspx.
  3. Typemock Isolator - This is a commercial framework. You can download a trial version from http://www.typemock.com/.

In this tutorial, I decided to use Moq. However, you could just as easily use Rhino Mocks or Typemock Isolator to create the Mock objects for the Contact Manager application.

Before you can use Moq, you need to complete the following steps:

  1. .
  2. Before you unzip the download, make sure that you right-click the file and click the button labeled Unblock (see Figure 1).
  3. Unzip the download.
  4. Add a reference to the Moq assembly by right-clicking the References folder in the ContactManager.Tests project and selecting Add Reference. Under the Browse tab, browse to the folder where you unzipped Moq and select the Moq.dll assembly. Click the OK button.
  5. After you complete these steps, your References folder should look like Figure 2.

Unblocking Moq

Figure 01: Unblocking Moq(Click to view full-size image)

References after adding Moq

Figure 02: References after adding Moq(Click to view full-size image)

Creating Unit Tests for the Service Layer

Let s start by creating a set of unit tests for our Contact Manager application s service layer. We'll use these tests to verify our validation logic.

Create a new folder named Models in the ContactManager.Tests project. Next, right-click the Models folder and select Add, New Test. The Add New Test dialog shown in Figure 3 appears. Select the Unit Test template and name your new test ContactManagerServiceTest.cs. Click the OK button to add your new test to your Test Project.

Note

In general, you want the folder structure of your Test Project to match the folder structure of your ASP.NET MVC project. For example, you place controller tests in a Controllers folder, model tests in a Models folder, and so on.

Models\ContactManagerServiceTest.cs

Figure 03: Models\ContactManagerServiceTest.cs(Click to view full-size image)

Initially, we want to test the CreateContact() method exposed by the ContactManagerService class. We'll create the following five tests:

  • CreateContact() - Tests that CreateContact() returns the value true when a valid Contact is passed to the method.
  • CreateContactRequiredFirstName() - Tests that an error message is added to model state when a Contact with a missing first name is passed to the CreateContact() method.
  • CreateContactRequiredLastName() - Tests that an error message is added to model state when a Contact with a missing last name is passed to the CreateContact() method.
  • CreateContactInvalidPhone() - Tests that an error message is added to model state when a Contact with an invalid phone number is passed to the CreateContact() method.
  • CreateContactInvalidEmail() - Tests that an error message is added to model state when a Contact with an invalid email address is passed to the CreateContact() method..

The first test verifies that a valid Contact does not generate a validation error. The remaining tests check each of the validation rules.

The code for these tests is contained in Listing 1.

Listing 1 - Models\ContactManagerServiceTest.cs

using System.Web.Mvc;
using ContactManager.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace ContactManager.Tests.Models
{
    [TestClass]
    public class ContactManagerServiceTest
    {
        private Mock<IContactManagerRepository> _mockRepository;
        private ModelStateDictionary _modelState;
        private IContactManagerService _service;

        [TestInitialize]
        public void Initialize()
        {
            _mockRepository = new Mock<IContactManagerRepository>();
            _modelState = new ModelStateDictionary();
            _service = new ContactManagerService(new ModelStateWrapper(_modelState), _mockRepository.Object);
        }

        [TestMethod]
        public void CreateContact()
        {
            // Arrange
            var contact = Contact.CreateContact(-1, "Stephen", "Walther", "555-5555", "steve@somewhere.com");

            // Act
            var result = _service.CreateContact(contact);
        
            // Assert
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void CreateContactRequiredFirstName()
        {
            // Arrange
            var contact = Contact.CreateContact(-1, string.Empty, "Walther", "555-5555", "steve@somewhere.com");

            // Act
            var result = _service.CreateContact(contact);

            // Assert
            Assert.IsFalse(result);
            var error = _modelState["FirstName"].Errors[0];
            Assert.AreEqual("First name is required.", error.ErrorMessage);
        }

        [TestMethod]
        public void CreateContactRequiredLastName()
        {
            // Arrange
            var contact = Contact.CreateContact(-1, "Stephen", string.Empty, "555-5555", "steve@somewhere.com");

            // Act
            var result = _service.CreateContact(contact);

            // Assert
            Assert.IsFalse(result);
            var error = _modelState["LastName"].Errors[0];
            Assert.AreEqual("Last name is required.", error.ErrorMessage);
        }

        [TestMethod]
        public void CreateContactInvalidPhone()
        {
            // Arrange
            var contact = Contact.CreateContact(-1, "Stephen", "Walther", "apple", "steve@somewhere.com");

            // Act
            var result = _service.CreateContact(contact);

            // Assert
            Assert.IsFalse(result);
            var error = _modelState["Phone"].Errors[0];
            Assert.AreEqual("Invalid phone number.", error.ErrorMessage);
        }

        [TestMethod]
        public void CreateContactInvalidEmail()
        {
            // Arrange
            var contact = Contact.CreateContact(-1, "Stephen", "Walther", "555-5555", "apple");

            // Act
            var result = _service.CreateContact(contact);

            // Assert
            Assert.IsFalse(result);
            var error = _modelState["Email"].Errors[0];
            Assert.AreEqual("Invalid email address.", error.ErrorMessage);
        }
    }
}

Because we use the Contact class in Listing 1, we need to add a reference to the Microsoft Entity Framework to our Test project. Add a reference to the System.Data.Entity assembly.

Listing 1 contains a method named Initialize() that is decorated with the [TestInitialize] attribute. This method is called automatically before each of the unit tests is run (it is called 5 times right before each of the unit tests). The Initialize() method creates a mock repository with the following line of code:

_mockRepository = new Mock<IContactManagerRepository>();

This line of code uses the Moq framework to generate a mock repository from the IContactManagerRepository interface. The mock repository is used instead of the actual EntityContactManagerRepository to avoid accessing the database when each unit test is run. The mock repository implements the methods of the IContactManagerRepository interface, but the methods don t actually do anything.

Note

When using the Moq framework, there is a distinction between _mockRepository and _mockRepository.Object. The former refers to the Mock<IContactManagerRepository> class that contains methods for specifying how the mock repository will behave. The latter refers to the actual mock repository that implements the IContactManagerRepository interface.

The mock repository is used in the Initialize() method when creating an instance of the ContactManagerService class. All of the individual unit tests use this instance of the ContactManagerService class.

Listing 1 contains five methods that correspond to each of the unit tests. Each of these methods is decorated with the [TestMethod] attribute. When you run the unit tests, any method that has this attribute is called. In other words, any method that is decorated with the [TestMethod] attribute is a unit test.

The first unit test, named CreateContact(), verifies that calling CreateContact() returns the value true when a valid instance of the Contact class is passed to the method. The test creates an instance of the Contact class, calls the CreateContact() method, and verifies that CreateContact() returns the value true.

The remaining tests verify that when the CreateContact() method is called with an invalid Contact then the method returns false and the expected validation error message is added to model state. For example, the CreateContactRequiredFirstName() test creates an instance of the Contact class with an empty string for its FirstName property. Next, the CreateContact() method is called with the invalid Contact. Finally, the test verifies that CreateContact() returns false and that model state contains the expected validation error message "First name is required."

You can run the unit tests in Listing 1 by selecting the menu option Test, Run, All Tests in Solution (CTRL+R, A). The results of the tests are displayed in the Test Results window (see Figure 4).

Test Results

Figure 04: Test Results (Click to view full-size image)

Creating Unit Tests for Controllers

ASP.NETMVC application control the flow of user interaction. When testing a controller, you want to test whether the controller returns the right action result and view data. You also might want to test whether a controller interacts with model classes in the manner expected.

For example, Listing 2 contains two unit tests for the Contact controller Create() method. The first unit test verifies that when a valid Contact is passed to the Create() method then the Create() method redirects to the Index action. In other words, when passed a valid Contact, the Create() method should return a RedirectToRouteResult that represents the Index action.

We don t want to test the ContactManager service layer when we are testing the controller layer. Therefore, we mock the service layer with the following code in the Initialize method:

_service = new Mock();

In the CreateValidContact() unit test, we mock the behavior of calling the service layer CreateContact() method with the following line of code:

_service.Expect(s => s.CreateContact(contact)).Returns(true);

This line of code causes the mock ContactManager service to return the value true when its CreateContact() method is called. By mocking the service layer, we can test the behavior of our controller without needing to execute any code in the service layer.

The second unit test verifies that the Create() action returns the Create view when an invalid contact is passed to the method. We cause the service layer CreateContact() method to return the value false with the following line of code:

_service.Expect(s => s.CreateContact(contact)).Returns(false);

If the Create() method behaves as we expect then it should return the Create view when the service layer returns the value false. That way, the controller can display the validation error messages in the Create view and the user has a chance to correct that invalid Contact properties.

If you plan to build unit tests for your controllers then you need to return explicit view names from your controller actions. For example, do not return a view like this:

return View();

Instead, return the view like this:

return View("Create");

If you are not explicit when returning a view then the ViewResult.ViewName property returns an empty string.

Listing 2 - Controllers\ContactControllerTest.cs

using System.Web.Mvc;
using ContactManager.Controllers;
using ContactManager.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace ContactManager.Tests.Controllers
{
    [TestClass]
    public class ContactControllerTest
    {
        private Mock<IContactManagerService> _service;

        [TestInitialize]
        public void Initialize()
        {
            _service = new Mock<IContactManagerService>();
        }

        [TestMethod]
        public void CreateValidContact()
        {
            // Arrange
            var contact = new Contact();
            _service.Expect(s => s.CreateContact(contact)).Returns(true);
            var controller = new ContactController(_service.Object);
        
            // Act
            var result = (RedirectToRouteResult)controller.Create(contact);

            // Assert
            Assert.AreEqual("Index", result.RouteValues["action"]);
        }

        [TestMethod]
        public void CreateInvalidContact()
        {
            // Arrange
            var contact = new Contact();
            _service.Expect(s => s.CreateContact(contact)).Returns(false);
            var controller = new ContactController(_service.Object);

            // Act
            var result = (ViewResult)controller.Create(contact);

            // Assert
            Assert.AreEqual("Create", result.ViewName);
        }

    }
}

Summary

In this iteration, we created unit tests for our Contact Manager application. We can run these unit tests at any time to verify that our application still behaves in the manner that we expect. The unit tests act as a safety net for our application enabling us to safely modify our application in the future.

We created two sets of unit tests. First, we tested our validation logic by creating unit tests for our service layer. Next, we tested our flow control logic by creating unit tests for our controller layer. When testing our service layer, we isolated our tests for our service layer from our repository layer by mocking our repository layer. When testing the controller layer, we isolated our tests for our controller layer by mocking the service layer.

In the next iteration, we modify the Contact Manager application so that it supports Contact Groups. We'll add this new functionality to our application using a software design process called test-driven development.