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: