[MVC]ASP.NET MVC unit Test

ASP.NET MVC Framework – Part 2: Testing

Published: 27 Mar 2008
By: Simone Chiaretta
Download Sample Code

In this article Simone will cover one of the main reasons for adopting the ASP.NET MVC framework: testability.

Introduction

In the first part of this series we introduced the MVC pattern and how Microsoft implemented it in the ASP.NET MVC framework. In this part we will cover one of the most important aspects of the framework: unit testing.

In the last article we saw how the MVC pattern allows a better separation of concerns and decreases the coupling between presentation logic and infrastructure code: these 2 features make it easier than before testing an ASP.NET web application.

Before looking at how to test a Controller Action let's have a brief overview of what unit testing is and why it matters.

Note: This article is based on the version of ASP.NET MVC released during the MIX08 conference at the beginning of March '08. Things may change in the future. It is also different from the version used for my previous article, so the code I showed may not work with the current code drop..

Unit testing: What it is and why it's important to do

Hundreds of books and thousands of articles have been written on this topic, but I'll try to summarize here the main concepts behind using unit testing.

From Wikipedia: "Unit testing is a procedure used to validate that individual units of source code are working properly. A unit is the smallest part of an application. […] In object-oriented programming the smallest unit is a method."

This short definition includes two important concepts:

  • Each test must be independent from the others
  • Each test should isolate a single part of the program and show that this individual part is correct

Why should we want to unit test an application? Mainly because it's an automated way to make sure it is always behaving the way it is supposed to behave. This way we can make changes to the code and refactor pieces of the application without the fear of breaking something somewhere else in the application. This is particularly useful with Agile development procedures.

One of the main concepts was that we should be able to isolate each single part of the program: this seems an easy concept in theory but can be difficult to apply. Usually a method in the Business Logic layer instantiates a class that lives in the Data Access layer. This in turn uses a data access provider to get the real data out of the database. And last, the web form that sits on top of everything uses the Business Logic passing the inputs collected from the user on the site.

Without taking the appropriate precautions, while testing the methods in the Business Logic we will test the Data Access layer and the data access provider as well. And this is against one of the main concepts of unit testing: isolation. Why is this that bad? Because if the Business Logic test failed we would not know whether the problem was really in the Business Logic, or in the procedure that retrieved the data from the DB, or maybe it was the DBA that erroneously deleted a stored procedure from the database.

The reason why we cannot isolate the part to be tested is that our Business Logic object is "dependent" on certain other objects in the Data Access layer. If we looked at how the application is designed we would see that the BL "knows" the DA through his real name at compile time (they are tightly coupled). The solution to the problem is to design the application so that the two components are loosely coupled: the BL "knows" the DA only through the interface it implements and the real dependency is defined only at runtime. This allows us to use a SQL Data Access provider in the real application and to use a fake Data Access provider that always returns the same results in our tests.

How ASP.NET MVC facilitates testing

We just saw that to easily test an application it's needed that all its components can be "faked" out, and this is achieved when the component is not just a class, but implements a well known interface or extent some kind of abstract base class.

In the first article of this series I said that the "classic" approach to developing web applications with .NET, the WebForm approach, is not easily testable: it's because the main class of ASP.NET, System.Web.UI.Page, has dependencies to HttpContext, HttpResponse, HttpRequest, HttpCookie and all the other objects coming from the fact that the Page is being executed inside a web server. And all these classes are "just" concrete classes, and cannot be replaced by fake implementations for the purpose of testing the web applications outside the context of the web server.

To address this issue, the ASP.NET MVC framework introduces a series of "abstractions" in order to remove all the direct references to the HTTP runtime. If you look at the API you will see that methods accept things like HttpContextBase, HttpRequestBase and so on. This way all the classes representing the HTTP runtime environment can be replaced by fake implementations that always return pre-baked responses instead of interacting with the web server.

From version P2 all these abstractions are located in a separate assembly called System.Web.Abstractions.dll.

But enough with theoretical talking, let's see how to test an MVC application.

Creating a test project

In the first article of the series we created a sample ASP.NET MVC web application using a project template provided with the ASP.NET 3.5 extensions: ASP.NET MVC Web Application. In version P2 they removed the ASP.NET MVC Web Application and Test project template and introduced a wizard that pops up and asks you whether you want to create a test project and which testing framework you want to use. It only supports Visual Studio Unit Test, but other testing framework can be added as well.

Let's create our test project in the File> New> Project, and selecting the ASP.NET MVC Web Application project. Notice that the description says Preview 2: if you don't see this then you are probably still running the first release. In this case you have to download and install the new version first.

Figure 1: New Project Dialog

images/fig01.jpg

After you select the ASP.NET MVC application, a new dialog window comes out.

Figure 2: Create Test Project dialog

images/fig02.jpg

Let's select Yes, generate a unit test project and choose Visual Studio Unit Test.

At this point the project template will create a solution with two projects, the first with the same sample application that we created in the first article, the second with a sample testing project, with an empty fixture for the HomeController.

Inside this class we will write tests to prove the correctness of the Controller Actions. We are going to write a test to verify that the Index action of the HomeController really asks for the view named "Index" to be rendered, and that the About action will redirect to the Index action. This is the code of the controller we are going to test:

Listing 1: Controller to be tested

  1. public class HomeController : Controller  
  2. {  
  3. public void Index()      {  
  4.          /* Call the Model here */  
  5.          RenderView("Index");  
  6.       }  
  7.   
  8. public void About()      {  
  9.          /* Call the Model here */  
  10.          RedirectToAction("Index");  
  11.       }  
  12. }  

There are a few possible approaches for testing a controller action:

  • using the Extract and override call pattern (also called Test-Specific Subclass)
  • replacing all the external dependencies with mocked objects

Extract and Override Call

This testing pattern comes from a practice used when testing legacy code that doesn't come with mockable interfaces or base classes. ASP.NET MVC is not "legacy" code and comes with mockable dependencies, but, as we will see later, the amount of code you have to write to mock everything is a bit more that one would expect, so for shorter tests I prefer this approach.

The concept behind this approach is that you extract the method that is using the dependency that you want to remove and then you override it with an implementation specific for the test you are writing.

In the application code we make calls to the RenderView and RedirectToAction methods. These ones have dependencies to a few other objects both in the ASP.NET MVC framework (IViewEngine, RouteData and ControllerContext) and in the HTTP runtime (HttpResponse and HttpContext) because in the real application they have to render a view and redirect to another URL. But for the purpose of this test we only need to store the name of the view we are going to render and invoke the method specified as the action we want to redirect to.

Listing 2: Test Specific Sub-Class

  1. internal class HomeControllerForTest : HomeController  
  2. {  
  3.    public string SelectedViewName { getprivate set; }  
  4.   
  5.    protected override void RenderView(string viewName,  
  6.                                       string masterName, object viewData)  
  7.    {  
  8.       SelectedViewName = viewName;  
  9.    }  
  10.   
  11.    protected override void RedirectToAction(  
  12.                          System.Web.Routing.RouteValueDictionary values)  
  13.    {  
  14.       string action = (string)values["action"];  
  15.       GetType().GetMethod(action).Invoke(thisnull);  
  16.    }  
  17. }  

With this test-specific class in place now we can test the controller class:

Listing 3: Testing using the test-specific sub-class

  1. [TestMethod]  
  2. public void AboutOverride()  
  3. {  
  4.    HomeControllerForTest controller = new HomeControllerForTest();  
  5.   
  6.    controller.About();  
  7.   
  8.    Assert.AreEqual("Index", controller.SelectedViewName,  
  9.                               "Should have redirected to Index");  
  10. }  
  11.   
  12. [TestMethod]  
  13. public void IndexOverride()  
  14. {  
  15.    HomeControllerForTest controller = new HomeControllerForTest();  
  16.   
  17.    controller.Index();  
  18.   
  19.    Assert.AreEqual("Index", controller.SelectedViewName,  
  20.                               "Should have selected Index");  
  21. }  

As you can see the code is pretty straightforward:

  • Instantiate the test specific class
  • Call the method we want to test
  • Assert that the Index view has been rendered

This approach works but can leave a bad taste in the mouths of some people, since here you are testing something that is different from what you are putting in the working application. Furthermore it's a trick used when testing legacy code not written with testability in mind while ASP.NET MVC is a new library written to be easily testable. A better and more elegant approach is the one that involves mocking dependencies.

Mock Objects

As we said earlier, everything that interacts with a controller is referenced through its interface so we can make our own implementation of the ViewEngine in order to test that the view that the controller wants to render is the one we are expecting.

Listing 4: FakeViewEngine

  1. public class FakeViewEngine : IViewEngine  
  2. {  
  3.     public ViewContext ViewContext { getprivate set; }  
  4.   
  5.     public void RenderView(ViewContext viewContext)  
  6.    {  
  7.       ViewContext = viewContext;  
  8.    }  
  9. }  

The only method that is specified inside the IViewEngine interface is RenderView. A real View Engine should select the view to be rendered based on the ViewContext passed, but for the purpose of this test we just need to store the ViewContext so that we can check that the ViewName is the one we are expecting.

But there is another step to be taken: even if we are not dealing with HttpContext and ControllerContext directly in our code, the framework needs this information in order to populate the ViewContext that is passed to the View Engine. So we have to mock them out.

I'll not discuss about what a mock object is in detail but here is a short definition: mock objects are objects that mimic the behavior of real objects and can also do some assertions on how methods are called. In the references you'll find some links to other articles on mocking, as well as on unit testing.

Let's have a look at how to test the Index controller action using mock objects. There are a few mocking framework around: I'm going to use RhinoMocks, but the same concepts apply to all of them.

Listing 5: Testing the Index action with RhinoMocks

  1. [TestMethod]  
  2. public void IndexMock()  
  3. {  
  4.    HomeController controller = new HomeController();  
  5.    var fakeViewEngine = new FakeViewEngine();  
  6.    controller.ViewEngine = fakeViewEngine;  
  7.   
  8.    MockRepository mocks = new MockRepository();  
  9.    using (mocks.Record())  
  10.    {  
  11.       mocks.SetFakeControllerContext(controller);  
  12.    }  
  13.    using (mocks.Playback())  
  14.    {  
  15.       controller.Index();  
  16.       Assert.AreEqual("Index", fakeViewEngine.ViewContext.ViewName,  
  17.                           "Should have selected Index");  
  18.    }  
  19. }  

First we created an instance of our controller class and set the view engine to use the FakeViewEngine we defined earlier.

Then RhinoMocks comes in:

  • We start setting expectations (mocks.Record).
  • We create a fake controller Context (We'll talk about this in a moment).

Then we put the mock repository in playback mode, we call the method we want to test and we assert that the ViewName that has been passed to the View Engine is the one we were expecting.

The SetFakeControllerContext method is a helper method that creates a fake HttpContext using RhinoMocks and then populates a controller context with all the other information needed by the framework code.

Listing 6: SetFakeControllerContext helper method

  1. public static void SetFakeControllerContext(this MockRepository mocks,  
  2.                                             Controller controller)  
  3. {  
  4.    var httpContext = mocks.FakeHttpContext();  
  5.    ControllerContext context = new ControllerContext(new RequestContext(httpContext,   
  6.                                                       new RouteData()), controller);  
  7.    controller.ControllerContext = context;  
  8. }  

This method, as well as other helper methods, is contained in the MvcMockHelpers class created by the ASP.NET MVC team.

In the code available for download there is a complete solution that includes a working sample of what we discussed here, and the testing method for the About action as well. The RedirectToAction method is still too coupled with the HTTP Runtime and in my opinion it still requires too much mocking. This is probably going to change in the next release so I prefer not writing the code here in the article. If you are curious have a look at the code.

Conclusions

In this article we introduced the problems related to unit testing and we saw how the ASP.NET MVC framework facilitates unit testing. We also saw how to test Controller Actions both using the Extract and Override Call approach and using Mock objects.

In the next article we will see how to pass data from the controller to the view and vice versa.

References

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏