SPS Home    >   Dgreath    >   C# MVC Unit Testing

C# MVC Unit Testing

The standard practice in MVC development is step by step incremental unit testing to assure that each method performs as expected in an isolated environment. This serves both the immediate goal of assuring a quality software product and to provide a baseline for future requalification as the result of down the road design changes. This document provides a step by step procedure for implementing unit testing in Visual Studio C# projects.

Step One: Create Test Project

Assuming you are in the project to be tested, start by creating a new test project in Visual Studio:

Test > Add New Test > Unit Test

Step Two: Reference the relevant MVC namespace

Right click on References in Solution Explorer then select Add New Reference. Navigate to the appropriate version of System.Web.Mvc and add it.

Step Three: Reference the project to be tested

Right click on References in Solution Explorer, then select Add New Reference. Navigate to the project to be tested and add it.

Step Four: Create tests

For each test to be created, right click Add, then Class, select Unit Test to create a new class. Each test will require its own class.

Step Five: Add reference to project's controller namespace

Add the following statement at the top of the page for each newly created class:

using <project namespace>.Controllers;

Step Six: Write the test

The standard class produced includes a constructor and skeletons for various properties. In most cases these can be deleted as the single method is the concern. The unit test is called an assertion and is built like a standard method except that the final line is an assert. Unit test assertions are marked with the [TestMethod()] attribute. There are fifteen (15) assertions and any can be used as needed including mulitple combinations The assertions are:

assert.AreEqual(object1, object2)
The value of Object1 is equal to object2.
The value of generic typed T1 is equal to T2.
The value of object1 is not equal to object2.
The value of generic typed T1 is not equal to T2.
Object1 and object2 are different objects.
Object1 and object2 are the same object.
Object1 and object2 hold the same value.
No test is performed. Always returns fail. Used to test how the assertion is handled
No test is performed. The assertion can't be verified. Used as a placeholder during develpment.
assert.IsFalse(Boolean expression)
The expression evaluates to false.
The object is of the specified type.
The specified object is not null.
The specified object is null.
assert.IsTrue(Boolean expression)
The expression evaluates to true.
Evaluates whether object1 and object2 are the same instance.

Examples of typical tests follow below.

Step Seven: Run the test

Test > Run > All Tests In Solution

Step Eight: Review results, make corrections, refactor

The initial test should be designed to fail the first time. This assures that the test is capable of detecting errors when they occur.Information reported from subsequent test execution should provide useful information as to why the test failed. Make the nessary corrections and re-run the test. Once all issues are resolved, then you can refactor and move on to the next test, retaiing this test for QA review and as a future baseline test.

Basic Test Examples

Test View Returned By Controller

The following method calls the controller "HomeController" and verifies that the returned view for the default case is "Index".

public void TestDefaultView()
   HomeController controller = new HomeController();
   ViewResult result = (ViewResult)controller.Index();
   assert.AreEqual("Index", result.ViewName);

Note: while the normal 'mapped' response would call the matching view implicity, for testing purposes the view needs to be specified i.e. return View("Index") to avoid a false fail.

Option: Messages can be included in the assertions to report specific reasons for failure like this:

   String errorMsg = "A call to the default view failed.";
   assert.AreEqual("Index", result.ViewName, errorMsg);

Test Data Returned By Controller

Here, a hypothetical ProductDetails method of the ProductController is called with a value of 5 passed in as the product id:

public void TestProductDetailsViewdata()
   int productId = 5;
   ProductController controller = new ProductController();
   ViewResult result = (ViewResult)controller.ProductDetails(productId);
   Product product = (Product)result.ViewData.Model;
   assert.AreEqual("Medium Blue Shirt", product.ProductName);

ProductDetails is calling into the database to fetch the data for productId 5 and returning it into a model called Product. The assert validates that the returned ProductName in the model is as expected.

Test Action Result Returned By Controller

Occasionally when processing an HttpPost, validation may determine that the process must be divert to another handler. The following method tests that behavior:
public void TestDefaultActionResult()
   HomeController controller = new HomeController();
   RedirectToRouteResult result = (RedirectToRouteResult)controller.Index();
   assert.AreEqual("Index", result.Values["action"]);

Test Json Result Returned By Controller

In this example a call into a Json controller will return a Json string to the response stream. Select one or more fields of interest in the Json payload and set up asserts for them. Any number of fields/asserts can be tested at one time.

public void TestJsonResult)
   String testValue = "ValueInFieldOfInterest";
   JsonController controller = new JsonController();
   JsonResult result = (JsonResult)controller.FieldOfInterest;
   String msg = ConvertToString(result.Data);
   assert.AreEqual(testValue, msg;

Test Business Layer Objects

Here, a hypothetical  business layer object "ProductDetails" method is called with a value of 5 passed in as the product id:

public void TestBusinessLayerObject()
   int id = 5;
   DataTable prodDetails = new BizLayer.ProductDetails(id);
   DataRow dr = prodDetails.Row[0];
   assert.AreEqual("Medium Blue Shirt", (String)dr["ProductName"]);

As many asserts can be included as needed.