Creating a Unit Test Project

The MS Test unit-testing framework is included with all paid editions of Visual Studio 2010 (it is not included in Visual Web Developer Express 2010); if you're using Visual Studio 2012, unit testing is even included in the free editions and contains a much-improved unit test runner. Although you can create unit test projects directly inside of Visual Studio, it can be a lot of work getting started with unit testing your MVC application. The ASP.NET MVC team included unit-testing capability in the New Project dialog for MVC applications, as shown in Figure 13.1.

By selecting the Create a Unit Test Project checkbox, you're telling the ASP.NET MVC New Project Wizard not only to create an associated unit test project, but also to populate it with a set of default unit tests. These default unit tests can help new users understand how to write tests against an MVC application.


Third-Party Unit-Testing Frameworks
The Test Framework combo box on the ASP.NET MVC New Project Wizard allows you to select which unit-testing framework you'd like to use. For users with the paid editions of Visual Studio, this will include a combo box, Visual Studio Unit Test, designed to be supplemented by third-party unit-testing frameworks. Check with your unit-testing framework of choice and see if it supports ASP.NET MVC.

Examining the Default Unit Tests

The default application templates give you just enough functionality to get you started with your first application. When you create the new project, it automatically opens HomeController.cs for you. HomeController.cs contains three action methods (Index, About, and Contact). This is the source for the Index action:

public ActionResult Index()
{
   ViewBag.Message = "Modify this template to jump-start
                      your ASP.NET MVC application.";

   return View();
}

This is fairly straightforward code. A welcome message is set into the weakly typed data sent to the view (the ViewBag object), and then a view result is returned. If you expected the unit tests to be relatively simple, you'd be right. In the default unit test project, there is exactly one test for the Index action:

[TestMethod]
public void Index()
{
   // Arrange
   HomeController controller = new HomeController();

   // Act
   ViewResult result = controller.Index() as ViewResult;

   // Assert
   Assert.AreEqual("Modify this template to jump-start your
       ASP.NET MVC application.", result.ViewBag.Message);
}

This is a pretty good unit test: it's written in 3A form, and at three lines of code, it's quite simple to understand. However, even this unit test has room for improvement. Our action method is only two lines of code, but it's actually doing three things:

  • It sets the welcome message into ViewBag.
  • It returns a view result.
  • The view result uses the default view.

For starters, you can see that this unit test is actually testing two of these three concerns (and it has a potential subtle bug, at that). Since you want your unit tests to be as small and single-focused as possible, you can see that you probably have at least two tests here (one for the message and one for the view result); if you wanted to write three, we wouldn't fault you for it.

The subtle bug in the test is the use of the as keyword. The as keyword in C# attempts to convert the value to the given type, and if it's not compatible, it returns null. However, in the assertion, the unit test dereferences the result reference without ever checking to see if it's null. Let's mark that up as a fourth concern to be tested: the action method should never return null.

The cast is an interesting code smell — that is, something you look at and wonder whether it's really the right thing. Is the cast really necessary? Obviously, the unit test needs to have an instance of the ViewResult class so that it can get access to the ViewBag property; that part isn't in question. But can you make a small change to the action code so that the cast is unnecessary? You can, and should:

public ViewResult Index()
{
   ViewBag.Message = "Modify this template to jump-start
                      your ASP.NET MVC application.";
   return View();
}

By changing the return value of the action method from the general ActionResult to the specific ViewResult, you've more clearly expressed the intention of your code: This action method always returns a view. Now you're down from four things to test to three with just a simple change of the production code. If you ever need to return anything else besides ViewResult from this action (for example, sometimes you'll return a view and sometimes you'll do a redirect), then you're forced to move back to the ActionResult return type. If you do that, it's very obvious that you must test the actual return type as well, because it won't always be the same return type.

Go ahead and rewrite the one test into two:

[TestMethod]

public void IndexShouldAskForDefaultView()
{
   HomeController controller = new HomeController();

   ViewResult result = controller.Index();

   Assert.IsNotNull(result);
   Assert.IsNull(result.ViewName);
}

[TestMethod]

public void IndexShouldSetWelcomeMessageInViewBag()
{
   HomeController controller = new HomeController();

   ViewResult result = controller.Index();

   Assert.AreEqual("Modify this template to jump-start your
       ASP.NET MVC application.", result.ViewBag.Message);
}

You should feel much better about these tests now. They're still simple, but they should be free of the subtle bugs that affected the other test, and you're clearly testing the two pieces of independent behavior that are happening in this action method. It's also worth noting that you've given the tests much longer and more descriptive names. I've found that longer names mean you're more likely to understand the reason a test fails without even needing to look at the code inside the test. You might have no idea why a test named Index might fail, but you have a pretty good idea why a test named IndexShouldSetWelcomeMessageInViewBag would fail.


Eliminating Duplication in the Unit Tests
You may have noticed that the two new unit tests have what you might call a significant overlap of code. With the production code, you will often refactor so that you can clean up the code and eliminate duplication. Should you do the same with unit tests?
You can, but you should be careful when and how you go about eliminating duplication. Most unit test frameworks have functionality that allows you to write code that executes before every test in a test class. This seems like an ideal place to move your duplicated code. For example, your two newly rewritten unit tests could be refactored like this:
[TestClass]
public class IndexTests
{
   private HomeController controller;
   private ViewResult result;

   [TestInitialize]
   public void SetupContext()
   {
       controller = new HomeController();

       result = controller.Index();
   }

   [TestMethod]
   public void ShouldAskForDefaultView()
   {
       Assert.IsNotNull(result);
       Assert.IsNull(result.ViewName);
   }

   [TestMethod]
   public void ShouldSetWelcomeMessageInViewBag()
   {
       Assert.AreEqual(”Modify this template to jump-start
                        your ASP.NET MVC application.”,
           result.ViewBag.Message);
   }
}
Is this better? On the good side, it certainly reduced the code duplication, but on the bad side, it's moved both your arrange and your act out of the test method. Removing the locality of the setup code can make the test harder to follow, especially as the size of your test class grows with many tests. The community seems to be split on whether you should keep the duplication in the name of clarity, or reduce the duplication in the name of maintenance.
If you plan to practice unit testing in this fashion, it's probably best to move to using one test class per context; in this case, context means common setup code. Instead of grouping all tests for one production class into a single test class, you group them based on the commonality of their setup code. Instead of test classes with names like PushTests, you end up with test classes like EmptyStackTests.
Trying to combine this kind of refactoring with “one test class per production class” is a recipe for disaster. As you add tens (or hundreds) of tests to a single test class, the necessary setup to support all those tests becomes overwhelming, and it won't be clear which lines of the setup code are needed for which unit tests. We strongly advise moving to something like test class per context for maintainability.

Test Only the Code You Write

One of the more common mistakes that people new to unit testing and TDD make is to test code they didn't write, even if inadvertently. Your tests should be focused on the code that you wrote, and not the code or logic that it depends upon.

For a concrete example, look at this example action method:

public ActionResult About()
{
   return View();
}

Action methods don't get much simpler than this. You should be able to get away with a fairly simple unit test for this code:

[TestMethod]
public void AboutShouldAskForDefaultView()
{
   HomeController controller = new HomeController();

   ViewResult result = (ViewResult)controller.About();

   Assert.IsNotNull(result);
   Assert.IsNull(result.ViewName);
}

When a controller action is invoked and a view is rendered by the MVC pipeline, a whole lot of stuff happens: Action methods are located by MVC, they are called with model binders invoked for any action parameters, the result is taken from the method and executed, and the resulting output is sent back to the browser. In addition, because you asked for the default view, that means the system attempts to find a view named About (to match your action name), and it will look in the ∼/Views/Home and ∼/Views/Shared folders to find it.

This unit test doesn't concern itself with any of that code. We focus on the code under test and none of its collaborators. Tests that test more than one thing at a time are called integration tests. If you look, there are no tests anywhere for that because all the rest of that behavior is provided by the MVC framework itself, and not any code you wrote. From a unit test perspective, you must trust that the MVC framework is capable of doing all those things. Testing everything running together is also a valuable exercise, but it's outside the scope of unit testing.

Let's focus for a moment on the ViewResult class. That is a direct result of calling the About action. Shouldn't you at least test its ability to look for the About view by default? You can say no, because it is code you didn't write (the MVC framework provided it), but even that argument isn't necessary. You can say no, even if it was your own custom action result class, because that's not the code you're testing right now. You are currently focused on the About action. The fact that it uses a specific action result type is all you need to know; exactly what it does is the concern of the unit test for that piece of code. You can safely assume, whether the action result is written by you or by the ASP.NET team, that the action result code is sufficiently tested on its own.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset