Mock HttpContext using Moq
In a project I’ve been working on, I needed a way to be able to test Windows authentication to an ASP MVC app. Having that authentication rest at the controller level made sense, but from a testing perspective it made things somewhat difficult. One aspect that wasn’t clear to me was mocking the _HttpContext_ on the controller that controls the authentication checks. Especially since getting that setup to run correctly without using mocking, makes the test more of an integration test, rather than a unit test. Even though controllers are meant to be lean from a logic sense, being able to test the actions respond with the correct response given a set of parameters can be useful.
I wanted to share the mocked context I came up with and how to use it so that if you need to unit test your controller using Windows authentication, it’s that much easier. I’ve seen numerous people asking about mocking the _User.Identity_ in the controller. This will explain how to do that.
I’m using Moq for my mocking. I don’t have a solid reason as to why I use it, just that’s what we’ve been using for our projects at the city so it’s what I learned to use. If you are using a different mocking framework, I’d love to hear about it.
Let’s get to it.
MockWebContext Class
I’ll go over it piece by piece. First, the class definition and the public properties:
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Moq;
namespace MyApp.Tests.Mocks
{
public class MockWebContext
{
public Mock<RequestContext> RoutingRequestContext { get; private set; }
public Mock<HttpContextBase> Http { get; private set; }
public Mock<HttpServerUtilityBase> Server { get; private set; }
public Mock<HttpResponseBase> Response { get; private set; }
public Mock<HttpRequestBase> Request { get; private set; }
public Mock<HttpSessionStateBase> Session { get; private set; }
public Mock<ActionExecutingContext> ActionExecuting { get; private set; }
public HttpCookieCollection Cookies { get; private set; }
}
}
The properties match the properties an HttpContext has. This way if we need to mock other aspects of it, we can. The most important one is the HttpContextBase (highlighted above). This is where the User object comes from that handles authentication.
Next, we are going to create two constructors. The first being the default constructor that just instantiates all the mocked properties:
public MockWebContext()
{
RoutingRequestContext = new Mock<RequestContext>(MockBehavior.Loose);
ActionExecuting = new Mock<ActionExecutingContext>(MockBehavior.Loose);
Http = new Mock<HttpContextBase>(MockBehavior.Loose);
Server = new Mock<HttpServerUtilityBase>(MockBehavior.Loose);
Response = new Mock<HttpResponseBase>(MockBehavior.Loose);
Request = new Mock<HttpRequestBase>(MockBehavior.Loose);
Session = new Mock<HttpSessionStateBase>(MockBehavior.Loose);
Cookies = new HttpCookieCollection();
RoutingRequestContext.SetupGet(c => c.HttpContext).Returns(Http.Object);
ActionExecuting.SetupGet(c => c.HttpContext).Returns(Http.Object);
Http.SetupGet(c => c.Request).Returns(Request.Object);
Http.SetupGet(c => c.Response).Returns(Response.Object);
Http.SetupGet(c => c.Server).Returns(Server.Object);
Http.SetupGet(c => c.Session).Returns(Session.Object);
Request.Setup(c => c.Cookies).Returns(Cookies);
Response.Setup(c => c.Cookies).Returns(Cookies);
}
We are really just mocking the various pieces of the HttpContext so that if any of them are accessed they return an empty mocked object. This is just to be used to test normal controller operations where authentication is not needed. Another place where you can do some cool things is with the Request object when doing tests. You can make the request return what you want to test malicious requests, malformed parameters, or other things.
Mocking Authentication
We need another constructor that will setup some authentication pieces. To explain it a little better, when doing Windows authentication, normally there’s a list of Active Directory groups/users added to the Web.config to limit access to certain people. In an ASP MVC controller, we can check if a user is in that list by doing:
[HttpGet]
public ActionResult Index()
{
return Content(User.Identity.IsAuthenticated ? 'You have access!' : 'Unauthorized');
}
The other method of checking a user’s authentication is the IsInRole method. This allows you to pass in the name of an Active Directory group and check if the user is in the group or not:
[HttpGet]
public ActionResult Index()
{
return Json(User.IsInRole("mygroup"), JsonRequestBehavior.AllowGet);
}
With that in mind, we will create a constructor that will setup these methods to create a HttpContext that can do fake authentication:
public MockWebContext(string user, string[] groups, bool isAuthenticated) : this()
{
Http.SetupGet(c => c.User.Identity.Name).Returns(user);
Http.SetupGet(c => c.User.Identity.IsAuthenticated).Returns(isAuthenticated);
foreach (var group in groups)
{
Http.Setup(c => c.User.IsInRole(group)).Returns(isAuthenticated);
}
}
This constructor calls the default constructor to set everything up, and then it mocks out the methods I mentioned above. I set it up to allow multiple groups to be passed in if there are multiple checks that are made. You can change it to only be a single group if you wanted.
That’s the basics of creating a fake HttpContext. There’s one more piece that I added that’s not necessary, but have become useful. I added two static methods to the class that creates a controller context.
public static ControllerContext BasicContext()
{
return new ControllerContext
{
HttpContext = new MockWebContext().Http.Object
};
}
public static ControllerContext AuthenticatedContext(string user, string[] groups, bool authenticated)
{
return new ControllerContext
{
HttpContext = new MockWebContext(user, groups, authenticated).Http.Object
};
}
Using the Mock
So let’s see how to use our fake HttpContext on a controller so that we can go about testing them with and without authentication. Obviously you’ll need to mock out any other dependencies the controller may have, but having a mock HttpContext, the controller can be tested in isolation.
Let’s say I want to create a test class for my Home controller. This is how I could setup the tests:
namespace MyApp.UnitTests
{
[TestClass]
public class HomeControllerTests
{
private HomeController _controller;
[TestInitialize]
public void Setup()
{
_controller = new HomeController { ControllerContext = MockWebContext.BasicContext() };
}
}
}
If you wanted for a few actions to have some authentication happen, just change it in your test:
[TestMethod]
public void Index_AuthenticatedUser_ReturnsView()
{
_controller.ControllerContext = MockWebContext.AuthenticatedContext("testuser", new[] { "mygroup" }, true);
var result = _controller.Index();
Assert.IsNotNull(result);
Assert.IsInstanceOfType(result, typeof (ViewResult));
}
Conclusion
That’s it. Mocking out that HttpContext is simple and I found it really useful to be able to test the authentication workflows that some of our controllers do.
If I did something wrong and you have a different method, I would love to hear about it.