Creating a Unit Test Project

The MSTest unit testing framework is included with all paid editions of Visual Studio 2010 (it is not included in Visual Web Developer Express 2010). 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 12.1.

By selecting the Create a Unit Test Project checkbox, you're telling the ASP.NET MVC New Project Wizard to not only 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. (If the checkbox isn't enabled, make sure you've selected either the Internet or Intranet template; there is no associated unit testing project for the Empty project template.)

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; for example, the xUnit.net unit testing framework (available at http://xunit.codeplex.com/) has built-in support for ASP.NET MVC 3 applications. After downloading the current version and unzipping it to your hard drive, run xunit.installer.exe and enable support for ASP.NET MVC 3 applications. These third-party frameworks work in all editions of Visual Studio 2010 including Visual Web Developer Express 2010.

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 two action methods (Index and About). This is the source for the Index action:

public ActionResult Index()
{
    ViewBag.Message = "Welcome to ASP.NET MVC!";

    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("Welcome to ASP.NET MVC!", 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). Because 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, I 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 = "Welcome to ASP.NET MVC!";
    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("Welcome to ASP.NET MVC!", 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 tests, 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("Welcome to ASP.NET MVC!",
                        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 of 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.

Only Test 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 the About method of the default HomeController class:

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. A unit test should test only the code under test and none of its collaborators (tests that test more than one thing at a time are called integration tests), so it's not appropriate here. 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
3.133.138.177