Subscribe to this Blog

Subscribe to this Blog

ASP.NET MVC Tip #12 – Faking the Controller Context - Stephen Walther on ASP.NET MVC

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:

http://weblogs.asp.net/stephenwalther/archive/2008/06/11/tdd-introduction-to-moq.aspx

http://weblogs.asp.net/stephenwalther/archive/2008/03/22/tdd-introduction-to-rhino-mocks.aspx

http://weblogs.asp.net/stephenwalther/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

Published Monday, June 30, 2008 5:26 PM by swalther
Filed under: , , ,

Comments

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

Monday, June 30, 2008 10:00 PM by Elijah Manor

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!

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

Tuesday, July 1, 2008 12:11 AM by Mojtaabaa

very good article.

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

Tuesday, July 1, 2008 1:00 PM by Webdiyer

Good article, thanks!

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

Wednesday, July 2, 2008 12:25 AM by shiju

Hi Stephen,

Good work. Congrats

Can we expect this type of tips from your upcoming MVC book?

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

Tuesday, July 8, 2008 5:01 PM by Max Pool

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.

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

Saturday, July 12, 2008 8:50 AM by REA_ANDREW

Does this also test the Cache object?

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

Monday, July 14, 2008 4:16 PM by swalther

@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!

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

Thursday, November 6, 2008 7:39 PM by sjeffrey

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:\source\MvcFakes\FakeControllerContext.cs 64 166 MvcFakes

This is using MVC beta1, should this code still work?

I looked for a newer version but failed.

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

Thursday, November 20, 2008 3:42 PM by umbyersw

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:\MvcRouteDebugger\CS\Tip29\MvcFakes\FakeControllerContext.cs 71 186 MvcFakes

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

Friday, December 5, 2008 5:03 PM by Chris Hoffman

This functionality exists in MVCContrib Google for TestControllerBuilder

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

Wednesday, May 25, 2011 2:56 AM by weblogs.asp.net

Asp net mvc tip 12 faking the controller context.. OMG! :)