CHAPTER 10
HttpContext.
Just the sound of that class makes the most seasoned .NET developers quake in their boots. HttpContext is the BIGGEST sealed object created in the history of mankind, a true Microsoft treasure. A treasure, unfortunately, all of us are stuck with.
The real problem with HttpContext isn’t the fact that it’s gigantic and sealed (although that doesn’t help), but the fact that it’s so easy to reach out and use HttpContext in an MVC controller, and not even know you’ve done it.
That being said, I have an assignment for you.
It’s to read this blog post on how to mock HttpContext. This is required reading to get the most out of this section, so please stop now, read the article in its entirety, and return to the book when you’re done.
Before reading this blog post, I didn’t realize trying to mock HttpContext defeated even the mighty Chuck Norris…but let’s talk a bit about the solution the author outlined.
This article shows how the author went about abstracting the pieces of HttpContext via an interface that allowed his previously untestable controller classes to be testable.
For example, the author created the ICurrentUser interface, which wrapped HttpContext calls and then injected that interface into the OrderController.
Maybe you were thinking of the FakeItEasy code you would write to fake that interface to allow the OrderController to be testable. This is a viable solution and will work, but let’s look at some of the drawbacks.
HttpContext is huge. If you need to fake more than just a couple calls to HttpContext.Current.User.IsInRole (like HttpRequest, HttpResponse, HttpSessionState, etc.), you’re going to end up either writing a huge interface with a bunch of members, which violates the interface segregation principle (http://www.objectmentor.com/resources/articles/isp.pdf), or writing a bunch of little interfaces that start to overwhelm your controller’s constructor with dependencies. You still need room in your controller’s constructor for other dependencies, like repositories.
There has to be a better way.
There is, using FakeItEasy MVC’s controller extensibility points, and new “Base” classes that .NET has added to its framework to allow testing of those sealed classes. Here’s how.
If we look at the ControllerBase class that all MVC controllers inherit from, we’ll see a property called ControllerContext.
public abstract class ControllerBase : IController { public ControllerContext ControllerContext { get; set; } public TempDataDictionary TempData { get; set; } public bool ValidateRequest { get; set; } public IValueProvider ValueProvider { get; set; } [Dynamic] public dynamic ViewBag { get; } public ViewDataDictionary ViewData { get; set; } protected virtual void Execute(System.Web.Routing.RequestContext requestContext); protected abstract void ExecuteCore(); protected virtual void Initialize(System.Web.Routing.RequestContext requestContext); } |
Code Listing 132: The ControllerContext property on the ControllerBase class
It is this extensibility point added by Microsoft to the controller inheritance structure that is going to allow us to use FakeItEasy to control things like HttpResponse, HttpRequest, HttpSessionState, and other sealed classes that FakeItEasy would otherwise not be able to control.
Let’s take a look the ControllerContext class.
public class ControllerContext { public ControllerContext(); protected ControllerContext(ControllerContext controllerContext); public ControllerContext(RequestContext requestContext, ControllerBase controller); public ControllerContext(HttpContextBase httpContext, RouteData routeData, ControllerBase controller); public virtual ControllerBase Controller { get; set; } public IDisplayMode DisplayMode { get; set; } public virtual HttpContextBase HttpContext { get; set; } public virtual bool IsChildAction { get; } public ViewContext ParentActionViewContext { get; } public RequestContext RequestContext { get; set; } public virtual RouteData RouteData { get; set; } } |
Code Listing 133: The ControllerContext class
We have four overloads for the constructor, including one that takes no parameters. We’re going to explore the overload that takes three parameters:
public ControllerContext(HttpContextBase httpContext, RouteData routeData, and ControllerBase controller).
But first, what is HttpContextBase? What happened to HttpContext?
Returning to the “Chuck Norris” example at the beginning of this chapter, Microsoft knew it needed to do something to allow MVC to be a unit-testing-friendly framework. Its current lineup of classes in the System.Web library consisted of all sealed classes, which are not testable by most mocking and faking frameworks.
As a result, they created “Base” classes that are exposed via the MVC framework. At runtime, these “Base” classes are delegating to the real static classes.
For example, here is the class declaration for HttpContext (un-testable):
public sealed class HttpContext : IServiceProvider, IPrincipalContainer |
Code Listing 134: Class declaration for HttpContext; not testable because it’s sealed
And here is the class declaration for HttpContextBase (testable):
public abstract class HttpContextBase : IServiceProvider |
Code Listing 135: Class declaration for HttpContextBase
Going all the way back to Chapter 3, “Introducing FakeItEasy,” we remember that you cannot fake a sealed class with FakeItEasy.
So what makes the members of HttpContextBase fake-able? Every one of them is declared as virtual. By declaring members as virtual, you allow FakeItEasy to be able to use them in configuration, behavior, and assertions. The same applies for the HttpRequestBase and HttpResponseBase classes and their respective untestable classes (HttpRequest and HttpResponse) they delegate to at runtime.
Now that we have a solid understanding of the “Base” classes in System.Web, let’s take a look at how to use FakeItEasy to set up the fakes we’ll need when creating a ControllerContext.
To start off, let’s build a VERY simple MVC controller and call it HomeController:
public class HomeController : Controller { [HttpPost] public void WriteToResponse() { Response.Write("writing to response"); } } |
Code Listing 136: The HomeController class
The WriteToResponse method on HomeController writes a string to the response stream. Usually, given the richness of MVC’s models and model-binding support, we rarely perform operations like this, but for the sake of the example, let’s stick use this action method as a starting point.
Here is the FakeItEasy setup method for testing the MVC action method in Code Listing 136:
[TestFixture] public class WhenWritingToResponse { private HttpResponseBase httpResponse; [SetUp] public void Given() { var sut = new HomeController(); var httpContext = A.Fake<HttpContextBase>(); httpResponse = A.Fake<HttpResponseBase>(); A.CallTo(() => httpContext.Response).Returns(httpResponse); var context = new ControllerContext(new RequestContext(httpContext, new RouteData()), sut);
sut.ControllerContext = context; sut.WriteToResponse(); } |
Code Listing 137: The setup method for the class testing HomeController
Newing up our SUT is done as it’s always been done—by newing it up directly, and then passing any dependencies to its constructor. Since HomeController takes no dependencies, creating the SUT is very straightforward in this example.
On the next line, you can see we’re creating a fake of the HttpContextBase class:
var httpContext = A.Fake<HttpContextBase>();
After we create a fake of HttpContextBase, we create a fake of HttpResponseBase:
httpResponse = A.Fake<HttpResponseBase>();
Once both of these fakes are created, we can now configure that calling the faked HttpContextBase’s Response property will return the faked HttpResponseBase.
Now we can finally get around to creating our ControllerContext class. We do so by passing in a new RequestContext, and passing the faked HttpContextBase, a new RouteData, and the controller that context will be used for into the constructor for RequestContext.
To finish up the test setup, we then assign the SUT’s ControllerContext property to the newly created ControllerContext object that we passed our configured fakes to. After we’ve done this, we invoke the WriteToResponse action method on our SUT.
What we need to test in this case is very, very simple. We want to assert that a message of “writing to response” was passed to the Write method of the Response object exactly once. Here is what the code looks like:
[Test] public void WritesToResponse() { A.CallTo(() => httpResponse.Write("writing to response")) .MustHaveHappened(Repeated.Exactly.Once); } |
Code Listing 138: Testing the that the correct string was written by Response.Write
Compared to our setup method, this code looks very familiar. Upon first glance, you can’t even tell we’re testing an MVC controller; the code reads like most of the other test methods we’ve seen in the book so far.
But what about testing something more complex than writing to the response stream? Let’s expand our example to perform some more of the more common MVC tasks.
Many times, we’re working with Session in our controllers. Whether that means reading or writing from it, Session is another very easy item in HttpContext to reach out to and use. Fortunately, Microsoft has provided the HttpSessionStateBase class in which all of its members are declared as virtual.
Here is a new action method where we’re adding a new item to Session:
[HttpPost] public void AddCustomerEmailToSession(string customersEmail) { Session.Add("CustomerEmail", customersEmail); } |
Code Listing 139: The new AddCustomerEmailToSession action method
And here is our new test setup to include the session state in our faked HttpContextBase:
[TestFixture] { private const string customerEmail = "[email protected]"; private HttpSessionStateBase httpSession; [SetUp] public void Given() { var sut = new HomeController(); var httpContext = A.Fake<HttpContextBase>(); var httpResponse = A.Fake<HttpResponseBase>(); httpSession = A.Fake<HttpSessionStateBase>();
A.CallTo(() => httpContext.Response).Returns(httpResponse); A.CallTo(() => httpContext.Session).Returns(httpSession); var context = new ControllerContext(new RequestContext(httpContext, new RouteData()), sut); sut.ControllerContext = context; sut.AddCustomerEmailToSession(customerEmail); } |
Code Listing 140: The test setup including a faked HttpSessionStateBase
We’re creating a fake of HttpSessionStateBase and then setting that fake to be returned when the Session property on the faked HttpContextBase is invoked. By building up this “chain” of fakes, we now have full control of the most common calls made from HttpContext in the controller.
Here is the test that asserts we added the customer’s email address to Session:
[Test] public void AddCustomerEmailToSession() { A.CallTo(() => httpSession.Add("CustomerEmail", customerEmail)) .MustHaveHappened(Repeated.Exactly.Once); } |
Code Listing 141: The unit test for adding customer email to Session
So far, we’ve look at how to fake HttpContextBase, HttpRequestBase, HttpResponseBase, and HttpSessionStateBase. This covers a good amount of the type of code you’ll be using in an MVC Controller, but there is one large part of an MVC application we have not talked about yet, and that is Authentication and Authorization. Let’s take a look at how we can unit test code that uses IPrincipal in a controller’s action method.
Note: You can find more information on IPrincipal here: https://msdn.microsoft.com/en-us/library/system.security.principal.iprincipal(v=vs.110).aspx.
Let’s say that before we send an email, we want to reach out to the User property on the controller class and grab the current name of the authenticated user. We do this by accessing User.Identity.Name in our controller’s action method. Here is an example using our current HomeController.
I’ve added the ISendEmail interface to this example and injected it into HomeController’s constructor.
public class HomeController : Controller { private readonly ISendEmail sendEmail; public HomeController(ISendEmail sendEmail) { this.sendEmail = sendEmail; } [HttpPost] public void SendCustomerEmail(string to) { var user = User.Identity.Name; sendEmail.SendEmailTo("[email protected]", to, string.Format("This email is intended for {0}", user), "this is an email"); } |
Code Listing 142: Using User.Identity.Name to get the name of the authenticated user
You can see where we reach out to User.Identity.Name to get the name of the currently authenticated user. The User property is exposed as a read-only property off of the public abstract class Controller class that all controllers inherit from by default.
The property on the base controller class is of type IPrinciple:
public IPrincipal User { get; } |
Code Listing 143: The User property of the abstract controller class
Looking at IPrincipal, we see the following:
public interface IPrincipal { IIdentity Identity { get; } bool IsInRole(string role); |
Code Listing 144: The IPrincipal interface
From this interface definition, you can see the Identity property, which is a type of IIdentity.
public interface IIdentity { string AuthenticationType { get; } bool IsAuthenticated { get; } string Name { get; } } |
Code Listing 145: IIdentity interface
At runtime, .NET provides an implementation of IPrincipal, but when unit testing, it does not. Let’s take a look at how to use FakeItEasy to fake IPrincipal to make the SendCustomerEmail action method testable.
Here is our test setup:
[TestFixture] public class WhenSendingCustomerEmail { private ISendEmail emailSender; private const string emailAddress = "[email protected]"; private const string userName = "UserName"; [SetUp] public void Given() { emailSender = A.Fake<ISendEmail>(); var sut = new HomeController(emailSender); sut.ControllerContext = new ControllerContext(new RequestContext( A.Fake<HttpContextBase>(), new RouteData()), sut); var principal = A.Fake<IPrincipal>(); var identity = new GenericIdentity(userName); A.CallTo(() => principal.Identity).Returns(identity); A.CallTo(() => sut.ControllerContext.HttpContext.User).Returns(principal); sut.SendCustomerEmail(emailAddress); } |
Code Listing 146: Unit test setup for controller action method using User.Identity.Name
Since we’ve added the ISendEmail interface to be used by the controller’s action method, we first create a fake of ISendEmail, and pass that fake to the HomeController’s constructor. We’ll be using that configured fake for assertion in our test method.
Next, we’ve simplified the setup of the ControllerContext here mainly because the only thing we really need is a faked HttpContextBase. We don’t need to set up a fake HttpRequestBase, HttpResponseBase, or HttpSessionStateBase because we’re not using any of that code in our current action method under test.
After the ControllerContext is set on our SUT, we create a fake of IPrincipal, create a GenericIdentity, and then return that identity when the Identity property on our faked IPrincipal is called. The final line of code configures the User property on our ControllerContext.HttpContext property to return our faked IPrincipal.
Finally, now that we have our fakes created and configured, we call our SUT’s SendCustomerEmail method, passing it an email address.
Here is the unit test method:
[Test] public void SendsEmailToCustomerWithUserNameInSubject() { A.CallTo(() => emailSender.SendEmailTo("[email protected]", emailAddress, string.Format("This email is intended for {0}", userName), "this is an email")) .MustHaveHappened(Repeated.Exactly.Once); } |
Code Listing 147: The unit test for SendCustomerEmail
We use our faked EmailSender to assert that SendEmailTo was invoked with the correct arguments, and one of those arguments includes the username of the authenticated user, which we configured via our faked IPrincipal.
To begin with, what is UrlHelper? If you’ve had to construct a URL while in a controller action method, and you’ve written code like the following, then you’ve used UrlHelper before.
var returnUrl = Url.Action("Index", "Home", null, Request.Url.Scheme); |
Code Listing 148: Using UrlHelper in a controller
If you were to click F12 with your cursor over the “Url” part of Url.Action, you’d find yourself in the abstract Controller base class’s Url property:
public UrlHelper Url { get; set; } |
Code Listing 149: Rhe Url property is type of UrlHelper
Now that we know what UrlHelper is, let’s take a look at how to test it with FakeItEasy.
Let’s return to our HomeController class and add another action method named BuildUrl.
public ActionResult BuildUrl() { var model = new BuildUrl { Url = Url.Action("Index", "Home", null, Request.Url.Scheme) }; return View(model); } |
Code Listing 150: The BuildUrl action method
The BuildUrl method creates and populates a BuildUrl model class, and then returns the populated model in the returned view. Here is the BuildUrl model class.
public class BuildUrl { public string Url { get; set; } } |
Code Listing 151: The BuildUrl model class
Note the use of Url.Action as well as Request.Url.Scheme in the BuildUrl method. We’ll need fakes of both of these items in order to make this method testable. Let’s start with the setup method for a unit test for this action method.
[TestFixture] public class WhenBuildingUrl { private string fullyQualifiedUrl; const string uri = "http://www.somewhere.com"; [SetUp] public void Given() { sut = new HomeController(); var httpContext = A.Fake<HttpContextBase>(); var httpRequest = A.Fake<HttpRequestBase>(); var httpResponse = A.Fake<HttpResponseBase>(); A.CallTo(() => httpContext.Request).Returns(httpRequest); A.CallTo(() => httpContext.Response).Returns(httpResponse); var context = new ControllerContext(new RequestContext(httpContext, new RouteData()), sut); sut.ControllerContext = context; var fakeUri = new Uri(uri); A.CallTo(() => sut.ControllerContext.RequestContext.HttpContext.Request.Url) .Returns(fakeUri); fullyQualifiedUrl = string.Format("{0}/home/index", uri); sut.Url = A.Fake<UrlHelper>(); A.CallTo(() => sut.Url.Action(A<string>.Ignored, A<string>.Ignored, null, A<string>.Ignored)).Returns(fullyQualifiedUrl); } } |
Code Listing 152: The test setup for BuildUrl
Let’s explore some of the differences in setup from what we’ve seen so far in this chapter for faking UrlHelper:
The rest of the test setup is very similar to previous examples in this chapter.
Let’s take a look at our test method.
[Test] public void ReturnsTheCorrectUrl() { var result = (ViewResult)sut.BuildUrl(); var model = (BuildUrl)result.Model; Assert.That(model.Url, Is.EqualTo(fullyQualifiedUrl)); } |
Code Listing 153: The assertion for testing BuildUrl
Here, we’re asserting that the Url property of the model is equal to the fullyQualifiedUrl that we set up in our unit test setup method.
In this chapter, we’ve seen how to use FakeItEasy in conjunction with MVC’s newest extensibility points and .NET’s base classes to created fully testable controller action methods. We covered how to set up and work around most of Microsoft’s big “sealed” classes to allow testability across all your MVC action methods. As you progress through writing tests for your controllers in your system, you’ll start to write a LOT of code that looks the same. Feel free to experiment with putting the fake setup calls into a shared library, or, like we did at my current job, write some extension methods for the Controller class that live in your unit testing project.