Testing Controllers in ASP.NET MVC
 
Published: 14 Jul 2009
Unedited - Community Contributed
Abstract
This article is taken from the book ASP.NET MVC in Action from Manning Publications. It addresses testing controllers. The focus of the Model-View-Controller pattern is the controller. Every screen is backed by a controller and rendered by a view. Without the controller, presentation logic would move to the view. Armed with test-driven development and a disciplined approach to separation of concerns, you will ensure the maintainability of your presentation layer. For the book’s table of contents, the author Forum, and other resources, go to http://manning.com/palermo/.
by Manning Publications
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 24370/ 19

Introduction

Excerpted from ASP.NET MVC in Action

Writing automated tests for all code in a code base is a best practice. It provides great feedback when the test suite is run multiple times per day. If you are not doing it now, you should start immediately. You have several popular, high quality frameworks for automated testing available to you including NUnit and MbUnit. At the time of writing, NBehave, MSTest and xUnit are also available, but they do not have nearly the widespread adoption of NUnit or MbUnit. All of these are free, with the exception of MSTest, which requires the purchase of Visual Studio, and they make testing code quite simple. What we are concerned with here in this article is testing controllers. There are different types of automated testing, and we are concerned with only one type at this point: unit testing. Unit tests run fast because they do not call out of process. In a unit test, dependencies are simulated so the only production code running is the controller code. In order for this to be possible the controllers have to be well designed. A well designed controller is loosely coupled with its dependencies. A well designed controller uses dependencies but is not in charge of locating or creating those dependencies. A well designed controller has clear responsibilities and only handles logic relevant to serving a web request. Some things a well designed controller does not do are file I/O, database access, web service calls, and thread management. This is not an all-inclusive list, but the listed things make it very difficult, if not impossible to write an automated unit test for the controller. The controller may very well call a dependency that does these things, but the controller itself should only be responsible for interaction with the dependency, not performing the fine-grained work. This is very important to testing because good design and testing go hand in hand. It is very difficult to test poorly designed code. In this article, we will walk through testing our viewless RedirectController.

Testing the RedirectController

The RedirectController must find the next conference and issue a redirect to another url so that a single conference can be displayed on the screen. This controller must find the conference and ask for a redirect to the action that can take it from there. The ASP.NET MVC Framework provides a redirect mechanism so that we need not use Response.Redirect() explicitly. In listing 1, we set up a unit test for this code along with some fake implementations of the dependencies on which the RedirectController relies.

Listing 1 – RedirectControllerTester: Ensuring we redirect to the correct url

using System; 
using System.Web.Mvc; 
using CodeCampServer.Core.Domain; 
using CodeCampServer.Core.Domain.Model;
using NUnit.Framework; 
using NUnit.Framework.SyntaxHelpers;
namespace MvcInAction.Controllers.UnitTests 
{ 
      [TestFixture] 
      public class RedirectControllerTester 
      {
            [Test] 
            public void ShouldRedirectToTheNextConference() 
            { 
                  //arrange the set up for the text 
                  var conferenceToFind = 
                        new Conference{Key = "thekey", Name = "name"}; 
                  var repository = new ConferenceRepositoryStub(conferenceToFind); 
 
                  //create class under test using simulated dependencies 
                  var controller = new RedirectController(repository); 
 
                  //act - exercise the class under test 
                  RedirectToRouteResult result = controller.NextConference(); 
 
                  //assert - ensure the operation did everything expected 
                  Assert.That(result.RouteValues["controller"],
                        Is.EqualTo("conference")); 
                  Assert.That(result.RouteValues["action"], Is.EqualTo("index")); 
                  Assert.That(result.RouteValues["conferenceKey"], 
                        Is.EqualTo("thekey")); 
            }
 
            private class ConferenceRepositoryStub : IConferenceRepository 
            { 
                  private readonly Conference _conference; 
 
                  public ConferenceRepositoryStub(Conference conference) 
                  { 
                        _conference = conference; 
                  }
 
                  public Conference GetNextConference() 
                  {
                        return _conference; 
                  }
 
                  public Conference[] GetAllForUserGroup(UserGroup usergroup) 
                  { 
                        throw new NotImplementedException(); 
                  }
 
                  public Conference[] GetFutureForUserGroup(UserGroup usergroup) 
                  { 
                        throw new NotImplementedException(); 
                  } 
 
                  public Conference GetById(Guid id) 
                  { 
                        throw new NotImplementedException(); 
                  } 
 
                  public void Save(Conference entity) 
                  {
                        throw new NotImplementedException(); 
                  }
 
                  public Conference[] GetAll() 
                  {
                        throw new NotImplementedException(); 
                  }
 
                  public void Delete(Conference entity) 
                  {
                        throw new NotImplementedException(); 
                  }
 
                  public Conference GetByKey(string key) 
                  {
                        throw new NotImplementedException(); 
                  }
            }
      }
}

Notice that most of the code listing is test double code, and not the RedirectController test itself. We have to stub out an IConferenceRepository implementation because calling that interface inside the controller action provides the conference that is next. How it performs that search is beyond the scope of this article and is irrelevant to the controller. When glancing at this test, you probably think that it is too complex for a single unit test. We will shortly see how to reduce the amount of code in the unit test fixture. It starts with making dependencies explicit.

Making Dependencies Explicit

You probably notice that there are only three real lines of code in the RedirectController. The controllers should all be quite thin, and this is a good example of that. The logic for finding the correct Conference object does not belong in the controller, so it is factored into a repository object. The only logic that belongs is the logic related to presenting information to the user. In this case, the user experiences a redirect. This controller demonstrates proper separation of concerns, and it is easily unit tested because it is only concerned with a single responsibility. We are able to simulate dependencies using test doubles.

Figure 1 Redirect test passing

In Figure 1, you see the unit test passing because we were able to properly simulate this controller’s dependencies and verify that given the dependencies, this controller will do its job correctly.

Using Test Doubles, Such as Stubs and Mocks

As far as the controller is concerned, its caller is passing in an implementation of the necessary interface. This interface is a dependency, and the controller will make use of it in an action method. How the dependency is passed in or what class implements the interface is irrelevant. At runtime, a production class will be passed into the controller, but at the time of unit testing, we use stand-in objects, or test doubles, to simulate the behavior of the dependencies. There are different types of simulated objects, and some of the meanings overlap. There are entire books written about testing and how to separate code for testing using fakes, stub, and mocks, and if you are interested in exploring the subject further, we would highly recommend reading Michael Feather’s book, Working Effectively with Legacy Code. In short, a fake, or test double, is a pretty generic term used to mean a non-production implementation of an interface or derived class serving the purpose of a stand-in for the real thing. Stubs are classes that return hard-code information just for the purpose of being called. The ControllerRepositoryStub shown in listing 1 is an example of a stub. A mock is a recorder. It will remember being called so that we can assert the behavior later on. It will remember arguments passed in and other details depending on what capability has been programmed into it.

A downside to using coded stubs and mocks is that you have many lines of code just to satisfy an interface implementation that may have six methods. This is not the only option, however. A favorite library for automating the creation of mocks and stubs is Rhino Mocks, originally written by Oren Eini. Rhino Mocks drastically reduces the number of lines of code in a unit test fixture by streamlining the creating of test doubles. If code is designed so that all dependencies are injected into the constructor, as shown in listing 2, unit testing because very easy to do and soon becomes a repetitive pattern of faking our dependencies and writing assertions. Over time, if you employ this technique, you will see a marked improvement in the quality of your code.

Listing 2 – Controllers can define dependencies in the constructor

public RedirectController(IConferenceRepository conferenceRepository) 
{ 
      _repository = conferenceRepository; 
}

For just a minute remember back to how many line of code we wrote for a stubbed implementation of IConferenceRepository. Now, examine listing 3 and notice how short this code listing is compared to the previous ones. Rhino Mocks supports setting up dynamic stub as well as dynamic mocks. The lines with “Stub(…)” are used for stubbing method or property to always return a given object. By using the Rhino Mocks library, we are able to provide dependency simulations quickly for easy unit testing.

Listing 3 – Using Rhino Mocks to streamline code necessary for fakes

using System.Web.Mvc; 
using CodeCampServer.Core.Domain; 
using CodeCampServer.Core.Domain.Model; 
using NUnit.Framework; 
using NUnit.Framework.SyntaxHelpers; 
using Rhino.Mocks; 
 
namespace MvcInAction.Controllers.UnitTests 
{ 
      [TestFixture] 
      public class RedirectControllerTesterWithRhino 
      {
            [Test]
            public void ShouldRedirectToTheNextConference()
            {
                  //arrange
                  var conferenceToFind = 
                        new Conference {Key = "thekey", Name = "name"};
                  // create a stubbed implementation of the interface using
                  // Rhino Mocks
                  var repository =                                
                        MockRepository.GenerateStub<IConferenceRepository>();
                  //intruct the stub to return a specific conference when called
                  repository.Stub
                        (r => r.GetNextConference()).Return(conferenceToFind);
 
                  //act
                  var controller = new RedirectController(repository);
                  RedirectToRouteResult result = controller.NextConference();
 
                  //assert - ensure the operation did everything expected
                  Assert.That(result.RouteValues["controller"],
                        Is.EqualTo("conference"));
                  Assert.That(result.RouteValues["action"], Is.EqualTo("index"));
                  Assert.That(result.RouteValues["conferenceKey"],
                        Is.EqualTo("thekey"));
            } 
      } 
}

A dynamic mocking library like Rhino Mocks is not appropriate in every unit testing scenario. The usage in listing 3 is the bread-and-butter scenario that saves a lot of setup code inside unit tests. More complex needs can quickly stress the Rhino Mocks API and become hard to read. While Rhino Mocks does have support for almost everything you could want to do, readability of the tests is important to maintain. When you need to assert method parameters of dependencies or do something special, do not be afraid to push Rhino Mocks to the side and leverage a concrete mock or stub in order to keep the test readable.

Elements of a Good Controller Unit Test

If you are just getting started with unit testing you might run into some common pitfalls and get a sour taste in your mouth. Again, this is not meant to be an entire course on testing. There are already entire books on that. This is specifically regarding writing unit tests for controller classes. We write so heavily on testing controller classes because test-driving the controllers ensures they have a good and proper design. It is nearly impossible to test-drive code that ends up with a bad design. Code with bad design tends not to be testable at all, so it is a very objective gauge. A good controller unit test will run very fast. We are talking 2000 unit tests all running within ten seconds. You might wonder how that is possible. It is possible because .Net code runs very fast, and if you are running unit tests, you are only waiting on the processor and RAM. Unit tests run code only within the AppDomain, so we do not have to deal with crossing AppDomain or Process boundaries. You can quickly sabotage this fast test performance if you break a fundamental run of unit testing, and that is allowing out-of-process calls. Out-of-process calls are orders of magnitude slower than in-process calls, and your test performance will suffer. Ensure that you are faking out all controller dependencies, and your test will continue to run very fast.

You also want your unit tests to be self-sufficient and isolated. You might see repeated code and think you need to refactor your unit tests. Resist this temptation and only create test helpers for the cross-cutting concerns. The DRY principle (Don’t Repeat Yourself) does not apply to test code as much as it does to production code. Rather, keeping test cases isolated and self-contained reduces the change burden when the production code needs to change. It is also more readable if you can scan a unit test and see the context all in one method.

The tests should also be repeatable. That means no shared global variables for test result state, and no shared state between tests in general. Keep a unit test isolated in every way, and it will be repeatable, order-independent and stable.

Conclusion

The final point you will want to pay attention to is pain. If your tests start to become painful, there is something wrong. The tests should enable development, not slow it down. If you are in a situation where you start to think that you could move faster without writing the tests, look for some technique errors or bad design in the production code. Get a peer to review the code. Correctly managed design and tests enable a fast sustained speed of development while poor testing techniques cause development to slow down to a point where testing is abandoned. At that point, it is back to painstaking, time-intensive manual testing.



User Comments

No comments posted yet.

Product Spotlight
Product Spotlight 





Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-18 6:08:22 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search