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/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:
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!
very good article.
Nice work, well done!
I hope this is baked in the actual framework when they RTM.
Good article, thanks!
Hi Stephen,
Good work. Congrats
Can we expect this type of tips from your upcoming MVC book?
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
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.
Does this also test the Cache object?
@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!
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.
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
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
Hi Stephen,
Great work!!
Is the FakeHttpSessionState thread safe? Can I use it to unit test multi-user scenario?
I really missed using controls when building an ASP.NET MVC application. In particular,…
Great post!! I added some stuff for faking the cache. It’s available here.
Excellent Post! Really such a nice information is given. I am looking for that one. Thanks once again Stephen.
Microsoft Enterprise Validation Application BlockI think this is the way to go
ne4 Thank you for sharing the this code.
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
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.
Thank you for sharing the this code, excellent post.
G
I believe it is a promising (currently version 4.0). So I would stick with it.. thanks
selam hi This sounds fascinating sıcak sohbet I’m going to read that tracing articlekısa aşk şiirleri when I have a moment.
Wow. erotik film izle is
şifalı bitkiler zayıflama de
çet sohbet fer
netlog ger
müzik dinle err
şarkı dinle
cüneyt arkın filmleri kk
isyan sözleri fer
hikayeler er
islami çet ff
adet sancısına ne iyi gelir hh
escort bayanlar der
bedava chat dd
chat odaları der
liseli kızlar derf
kızlarla sohbet fder
kızlarla chat
sohbet errJHGF
Thank you for sharing this articles6
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