ASP.NET MVC Tip #12 – Faking the Controller Context

In this tip, I show you how to test ASP.NET intrinsics when building unit tests for an ASP.NET MVC application. I show you how to create a standard set of fake objects that enables you to fake the current user, the current user roles, the request parameters, session state, and cookies.

An ASP.NET MVC application is infinitely more testable than an ASP.NET Web Forms application. Every feature of ASP.NET MVC was designed with testability in mind. However, there are still certain aspects of an ASP.NET MVC application that are difficult to test. In particular, you might discover that testing the ASP.NET instrinsics remains challenging in an ASP.NET MVC application.

What do I mean by an ASP.NET intrinsic? I’m referring to the objects that hang off of the HttpContext. I mean these objects:

· Request.Forms – The collection of form parameters posted to a page.

· Request.QueryString – The collection of query string parameters passed to a page.

· User – The current user making the page request.

· Request.Cookies – The browser cookies that are passed to a page.

· Session – The session state object.

Imagine, for example, that you want to test whether a particular controller action has, in fact, added a particular item to session state. You could create a unit test that looks like this:

VB Version

<TestMethod()> _
Public Sub TestSessionState()
  ' Arrange
  Dim controller = New HomeController()
 
  ' Act
  Dim result = TryCast(controller.TestSession(), ViewResult)
 
  ' Assert
  Assert.AreEqual("wow!", controller.HttpContext.Session("item1"))
End Sub

C# Version

[TestMethod]
public void TestSessionState()
{
    // Arrange
    var controller = new HomeController();
 
    // Act
    var result = controller.TestSession() as ViewResult;
 
    // Assert
    Assert.AreEqual("wow!", controller.HttpContext.Session["item1"]);
}

This test checks whether a controller action named TestSession() adds a new item to session state. The last statement in the test contains the assertion that there is a session state item named item1 that has the value “wow!”.

You could attempt to satisfy this test with the following controller action:

VB Version

Public Function TestSession() As ViewResult
    Session("item1") = "wow!"
    Return View()
End Function

C# Version

public ViewResult TestSession()
{
    Session["item1"] = "wow!";
    return View();
}

This controller action adds a new item to session state with the expected value.

Unfortunately, if you run the unit test, the test will fail. The test will fail with a NullReferenceException. The problem is that session state does not exist within the context of a unit test. In fact, none of the ASP.NET intrinsics exist within a test method. This means that you cannot test for cookies, form parameters, query string parameters, or the user identity or user roles.

Mocking versus Stubbing

If you need to write a unit test that uses the ASP.NET intrinsics then you must make a choice. Your two options are to use a Mock Object Framework or to use a set of fake classes.

One option is to mock the ASP.NET intrinsics by using a Mock Object Framework such as Moq, Typemock Isolator, or Rhino Mocks. You can use any one of these frameworks to generate objects that pretend to be the normal ASP.NET intrinsics. If you want to learn more, I have written blog entries on all three of these Mock Object Frameworks:

/Blog/archive/2008/06/11/tdd-introduction-to-moq.aspx

/blog/archive/2008/03/22/tdd-introduction-to-rhino-mocks.aspx

/blog/archive/2008/03/16/tdd-introduction-to-typemock-isolator.aspx

The other option is to build a set of classes that you can use to simulate the ASP.NET intrinsics. You can create this set of classes once and use the set of classes in all of your future ASP.NET MVC projects.

In this tip, I am going to take this second approach. I’m going to show you how you can easily test ASP.NET intrinsics without using a Mock Object Framework by creating a standard set of fakes for the ASP.NET intrinsics.

Creating a Fake Controller Context

At the end of this tip, you can click the download link to download the fake classes. I created a set of fake ASP.NET intrinsics with the following names:

· FakeControllerContext

· FakeHttpContext

· FakeHttpRequest

· FakeHttpSessionState

· FakeIdentity

· FakePrincipal

You can use these fakes by creating a new instance of the FakeControllerContext and assigning it to a controller’s ControllerContext property in a unit test. For example, here is how you can fake a particular user within a unit test by taking advantage of the FakeControllerContext class:

VB.NET Version

Dim controller = New HomeController()
controller.ControllerContext = New FakeControllerContext(controller, "Stephen")

C# Version

var controller = new HomeController();
controller.ControllerContext = new FakeControllerContext(controller, "Stephen");

After you assign the FakeControllerContext to a controller, the controller will use that context for the remainder of the unit test. Let’s look at specific examples of using the FakeControllerContext class to simulate different ASP.NET intrinsics.

Testing Form Parameters

Imagine that you want to test the behavior of a controller action when you pass different form parameters to the action. Furthermore, imagine that the controller action accesses the Request.Form collection directly like this:

VB.NET Version

Public Function Insert() As ActionResult
  ViewData("firstname") = Request.Form("firstName")
  ViewData("lastName") = Request.Form("lastName")
 
  Return View()
End Function

C# Version

public ActionResult Insert()
{
  ViewData["firstname"] = Request.Form["firstName"];
  ViewData["lastName"] = Request.Form["lastName"];
 
  return View();
}

How do you test this controller action? In this case, you can take advantage of the FakeControllerContext constructor that takes a collection of form parameters. Here’s a test that checks whether the firstName and lastName form parameters get into view data:

VB.NET Version

<TestMethod()> _
Public Sub TestFakeFormParams()
    ' Create controller
    Dim controller = New HomeController()
 
    ' Create fake controller context
    Dim formParams = New NameValueCollection()
    formParams.Add("firstName", "Stephen")
    formParams.Add("lastName", "Walther")
    controller.ControllerContext = New FakeControllerContext(controller, formParams)
 
    ' Act
    Dim result = TryCast(controller.Insert(), ViewResult)
    Assert.AreEqual("Stephen", result.ViewData("firstName"))
    Assert.AreEqual("Walther", result.ViewData("lastName"))
End Sub

C# Version

[TestMethod]
public void TestFakeFormParams()
{
    // Create controller
    var controller = new HomeController();
 
    // Create fake controller context
    var formParams = new NameValueCollection { { "firstName", "Stephen" }, {"lastName", "Walther"} };
    controller.ControllerContext = new FakeControllerContext(controller, formParams);
 
    // Act
    var result = controller.Insert() as ViewResult;
    Assert.AreEqual("Stephen", result.ViewData["firstName"]);
    Assert.AreEqual("Walther", result.ViewData["lastName"]); 
}

The form parameters for the FakeControllerContext are created with a NameValueCollection. This fake set of form data is passed to the constructor for the FakeControllerContext.

Testing Query String Parameters

Imagine that you need to test whether or not certain query string parameters are passed to a view. The query string parameters are accessed directly from the Request.QueryString collection. For example, the controller action might look like this:

VB Version

Public Function Details() As ViewResult
    ViewData("key1") = Request.QueryString("key1")
    ViewData("key2") = Request.QueryString("key2")
    ViewData("count") = Request.QueryString.Count
 
    Return View()
End Function

C# Version

public ViewResult Details()
{
  ViewData["key1"] = Request.QueryString["key1"];
  ViewData["key2"] = Request.QueryString["key2"];
  ViewData["count"] = Request.QueryString.Count;
            
  return View();
}

In this case, you can fake the query string parameters by passing a NameValueCollection to the FakeControllerContext class like this:

VB Version

<TestMethod()> _
Public Sub TestFakeQueryStringParams()
    ' Create controller
    Dim controller = New HomeController()
 
    ' Create fake controller context
    Dim queryStringParams = New NameValueCollection()
    queryStringParams.Add("key1", "a")
    queryStringParams.Add("key2", "b")
    controller.ControllerContext = New FakeControllerContext(controller, Nothing, queryStringParams)
 
    ' Act
    Dim result = TryCast(controller.Details(), ViewResult)
    Assert.AreEqual("a", result.ViewData("key1"))
    Assert.AreEqual("b", result.ViewData("key2"))
    Assert.AreEqual(queryStringParams.Count, result.ViewData("count"))
End Sub

C# Version

[TestMethod]
public void TestFakeQueryStringParams()
{
    // Create controller
    var controller = new HomeController();
 
    // Create fake controller context
    var queryStringParams = new NameValueCollection { { "key1", "a" }, { "key2", "b" } };
    controller.ControllerContext = new FakeControllerContext(controller, null, queryStringParams);
 
    // Act
    var result = controller.Details() as ViewResult;
    Assert.AreEqual("a", result.ViewData["key1"]);
    Assert.AreEqual("b", result.ViewData["key2"]);
    Assert.AreEqual(queryStringParams.Count, result.ViewData["count"]);
}

Notice that the query string parameters are passed as the second parameter of the FakeControllerContext’s constructor.

Testing Users

You might need to test the security of your controller actions. For example, you might want to display a certain view to only a particular authenticated user. The following controller action displays a Secret view to authenticated users and redirects everyone else away to the Index view:

VB Version

Public Function Secret() As ActionResult
    If User.Identity.IsAuthenticated Then
        ViewData("userName") = User.Identity.Name
        Return View("Secret")
    Else
        Return RedirectToAction("Index")
    End If
End Function

C# Version

public ActionResult Secret()
{
    if (User.Identity.IsAuthenticated)
    {
        ViewData["userName"] = User.Identity.Name;
        return View("Secret");
    }
    else
    {
        return RedirectToAction("Index");
    }
}

You can use the FakeController to test whether this action behaves as you expect like this:

VB Version

<TestMethod()> _
Public Sub TestFakeUser()
    ' Create controller
    Dim controller = New HomeController()
 
    ' Check what happens for authenticated user
    controller.ControllerContext = New FakeControllerContext(controller, "Stephen")
    Dim result = TryCast(controller.Secret(), ActionResult)
    Assert.IsInstanceOfType(result, GetType(ViewResult))
    Dim viewData As ViewDataDictionary = (CType(result, ViewResult)).ViewData
    Assert.AreEqual("Stephen", viewData("userName"))
 
    ' Check what happens for anonymous user
    controller.ControllerContext = New FakeControllerContext(controller)
    result = TryCast(controller.Secret(), ActionResult)
    Assert.IsInstanceOfType(result, GetType(RedirectToRouteResult))
End Sub

C# Version

[TestMethod]
public void TestFakeUser()
{
    // Create controller
    var controller = new HomeController();
 
    // Check what happens for authenticated user
    controller.ControllerContext = new FakeControllerContext(controller, "Stephen");
    var result = controller.Secret() as ActionResult;
    Assert.IsInstanceOfType(result, typeof(ViewResult));
    ViewDataDictionary viewData = ((ViewResult) result).ViewData;
    Assert.AreEqual("Stephen", viewData["userName"]);
 
    // Check what happens for anonymous user
    controller.ControllerContext = new FakeControllerContext(controller);            
    result = controller.Secret() as ActionResult;
    Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));
}
 

This test actually tests three things (it should probably be broken into multiple tests). First, it tests that an authenticated user gets a view back from the controller action. Furthermore, it checks whether the authenticated user name’s is added to view data successfully. Finally, it checks that anonymous users get redirected when the controller action is executed.

Testing User Roles

You might want to display different content to different users depending on their role. For example, certain content should only be viewable by administrators (Admins) for a website. Imagine that you have a controller action that looks like this:

VB Version

Public Function Admin() As ActionResult
    If User.IsInRole("Admin") Then
        Return View("Secret")
    Else
        Return RedirectToAction("Index")
    End If
End Function

C# Version

public ActionResult Admin()
{
    if (User.IsInRole("Admin"))
    {
        return View("Secret");
    }
    else
    {
        return RedirectToAction("Index");
    }
}

This controller action returns the Secret view only if you are a member of the Admin role.

You can test this controller action with the following test method:

VB Version

<TestMethod()> _
Public Sub TestFakeUserRoles()
    ' Create controller
    Dim controller = New HomeController()
 
    ' Check what happens for Admin user
    controller.ControllerContext = New FakeControllerContext(controller, "Stephen", New String() {"Admin"})
    Dim result = TryCast(controller.Admin(), ActionResult)
    Assert.IsInstanceOfType(result, GetType(ViewResult))
 
    ' Check what happens for anonymous user
    controller.ControllerContext = New FakeControllerContext(controller)
    result = TryCast(controller.Admin(), ActionResult)
    Assert.IsInstanceOfType(result, GetType(RedirectToRouteResult))
End Sub

C# Version

[TestMethod]
public void TestFakeUserRoles()
{
    // Create controller
    var controller = new HomeController();
 
    // Check what happens for Admin user
    controller.ControllerContext = new FakeControllerContext(controller, "Stephen", new string[] {"Admin"});
    var result = controller.Admin() as ActionResult;
    Assert.IsInstanceOfType(result, typeof(ViewResult));
 
    // Check what happens for anonymous user
    controller.ControllerContext = new FakeControllerContext(controller);
    result = controller.Admin() as ActionResult;
    Assert.IsInstanceOfType(result, typeof(RedirectToRouteResult));
}

This test verifies that only members of the Admin role can see the Secret view. The test also checks that anonymous users get redirected to another page.

Testing Browser Cookies

Imagine that you need to test actions that access browser cookies. For example, you might be passing a customer id around in a browser-side cookie. How do test this type of action?

The following controller action simply adds a browser cookie to view data:

VB Version

Public Function TestCookie() As ViewResult
    ViewData("key") = Request.Cookies("key").Value
    Return View()
End Function

C# Version

public ViewResult TestCookie()
{
    ViewData["key"] = Request.Cookies["key"].Value;
    return View();
}

You can test this controller action by creating a SessionItemCollection and passing the collection to the FakeControllerContext like this:

VB Version

<TestMethod()> _
Public Sub TestCookies()
    ' Create controller
    Dim controller = New HomeController()
 
    ' Create fake Controller Context
    Dim cookies = New HttpCookieCollection()
    cookies.Add(New HttpCookie("key", "a"))
    controller.ControllerContext = New FakeControllerContext(controller, cookies)
    Dim result = TryCast(controller.TestCookie(), ViewResult)
 
    ' Assert
    Assert.AreEqual("a", result.ViewData("key"))
End Sub

C# Version

[TestMethod]
public void TestCookies()
{
    // Create controller
    var controller = new HomeController();
 
    // Create fake Controller Context
    var cookies = new HttpCookieCollection();
    cookies.Add( new HttpCookie("key", "a"));
    controller.ControllerContext = new FakeControllerContext(controller, cookies);
    var result = controller.TestCookie() as ViewResult;
 
    // Assert
    Assert.AreEqual("a", result.ViewData["key"]);
}

This test verifies that the controller action does, in fact, add a cookie named key to view data.

Testing Session State

Final sample. Let’s look at how we can test a controller action that accesses session state:

VB Version

Public Function TestSession() As ViewResult
    ViewData("item1") = Session("item1")
    Session("item2") = "cool!"
    Return View()
End Function

C# Version

public ViewResult TestSession()
{
    ViewData["item1"] = Session["item1"];
    Session["item2"] = "cool!";
    return View();
}

This controller action both reads and writes to session state. An item named item1 is pulled from session state and added to view data. The controller action also creates a new session state item named item2.

We can test this controller action with the following unit test:

VB Version

<TestMethod()> _
Public Sub TestSessionState()
    ' Create controller
    Dim controller = New HomeController()
 
    ' Create fake Controller Context
    Dim sessionItems = New SessionStateItemCollection()
    sessionItems("item1") = "wow!"
    controller.ControllerContext = New FakeControllerContext(controller, sessionItems)
    Dim result = TryCast(controller.TestSession(), ViewResult)
 
    ' Assert
    Assert.AreEqual("wow!", result.ViewData("item1"))
 
    ' Assert
    Assert.AreEqual("cool!", controller.HttpContext.Session("item2"))
End Sub

C# Version

[TestMethod]
public void TestSessionState()
{
    // Create controller
    var controller = new HomeController();
 
    // Create fake Controller Context
    var sessionItems = new SessionStateItemCollection();
    sessionItems["item1"] = "wow!";
    controller.ControllerContext = new FakeControllerContext(controller, sessionItems);
    var result = controller.TestSession() as ViewResult;
 
    // Assert
    Assert.AreEqual("wow!", result.ViewData["item1"]);
 
    // Assert
    Assert.AreEqual("cool!", controller.HttpContext.Session["item2"]);
}

Notice that a SessionStateItemCollection is created and passed to the FakeControllerContext’s constructor. The SessionStateItemCollection represents all of the items in session state.

Conclusion

In this tip, I’ve demonstrated how you can test ASP.NET intrinsics — such as form parameters, query string parameters, user identity, user roles, cookies, and session state – by using a standard set of fake classes. You can download the complete source for these fake classes (both C# and VB.NET) by clicking the following link:

Download the Code

Discussion

  1. Elijah Manor says:

    That is awesome! I was banging my head today trying to figure out how I would test my controller that referenced a Session key. Great timing!

  2. Vesta says:

    very good article.

  3. http:// says:

    Nice work, well done!
    I hope this is baked in the actual framework when they RTM.

  4. Webdiyer says:

    Good article, thanks!

  5. Hi Stephen,

    Good work. Congrats
    Can we expect this type of tips from your upcoming MVC book?

  6. I think we should not access the statics like Session, User, Request, etc. in our controllers. Inject wrappers to them in the constructor of you controller and use an IoC container like Castle Windsor, StructureMap or Unity to resolve the dependencies. This way you don’t need to mock the ASP.NET specifics and testing is very easy.

    Cheers,
    Stefan Lieser

  7. Max Pool says:

    Great work – really hits the spot and allows me to gut all of my mocking code.

    Mocking has it’s places, but this is just so much more clean and straight forward to implement.

  8. REA_ANDREW says:

    Does this also test the Cache object?

  9. http:// says:

    @Andrew – You would need to fake the Cache object. You could use the same approach used in this tip for faking the Session object to fake the Cache object. Good point! Great idea for another tip!

  10. http:// says:

    I’m trying to use your MvcFakes project but I get this compilation error
    Error 2 Argument ‘3’: cannot convert from ‘System.Web.Mvc.IController’ to ‘System.Web.Mvc.ControllerBase’ C:sourceMvcFakesFakeControllerContext.cs 64 166 MvcFakes

    This is using MVC beta1, should this code still work?
    I looked for a newer version but failed.

  11. http:// says:

    I am getting the same error using the beta, same MvcFakes project as tip 29…

    Error 2 Argument ‘3’: cannot convert from ‘System.Web.Mvc.IController’ to ‘System.Web.Mvc.ControllerBase’ C:MvcRouteDebuggerCSTip29MvcFakesFakeControllerContext.cs 71 186 MvcFakes

  12. Hi Stephen

    Thanks for the MvcFake Library. I just downloaded it and adapted it to the latest version of ASP.NET MVC and extended the FakeHttpRequest a bit to support more of the HttpRequestBase properties. Let me know if you want to have my updated library and if you want to share it with others

    Cheers
    Andi

  13. Yogendra says:

    Hi Stephen,

    Great work!!

    Is the FakeHttpSessionState thread safe? Can I use it to unit test multi-user scenario?

  14. MOV to DVD says:

    I really missed using controls when building an ASP.NET MVC application. In particular,…

  15. Paul Brown says:

    Great post!! I added some stuff for faking the cache. It’s available here.

  16. Excellent Post! Really such a nice information is given. I am looking for that one. Thanks once again Stephen.

  17. Microsoft Enterprise Validation Application BlockI think this is the way to go

  18. ne4 Thank you for sharing the this code.

  19. Venkatesh says:

    Great work man!, I allmost wasted 2 days searching on net to find any good articles which help to mock ASP.NEt intrinsics that finally overed when i seen your aricle.

    I’ve question though, how can I read a value directly from Request object let say…

    strPageID = Request(“hndPageID”) — hndPageID hardcoded in .aspx page with under hidden field. (eg < %=Html.Hidden("hdnPageID", "4")%>)

    Thanks again

  20. However I am still curious about one thing (outside current topic, sorry): how much ASP.NET MVC depends on RESTful routes? Or in other words: can I use ASP.NET MVC with old good “~/MyDirectory/Default.aspx?id=333″ mapped directly to file system? It might be useful for step-by-step migration from WebForms.

  21. Thank you for sharing the this code, excellent post.

  22. G
    I believe it is a promising (currently version 4.0). So I would stick with it.. thanks

  23. Thank you for sharing this articles6

  24. HD Video Converter says:

    As the users of HD Camcorders like Sony, Canon, Panasonic, this HD Video Converter is necessary to help us convert hd Video easily and quickly. The Converter for HD provides several practical editing functions to help you achieve ideal output effect. Trim function is to cut videos into clips which you can just convert and transfer to your player. Crop function helps you remove black bars around the movie. You could use Effect function to adjust video brightness, contrast, saturation and more parameters. More powerful and considerate functions are waiting for you to explore.MKV Converter l FLV Converter l DVD Ripper 1