Summary
Anybody who has attempted writing unit tests for a website has run into the problem where you cannot mock the HttpContext object. I usually write my code in such a way that I don’t need to mock the context by using only connection code to go between the website controller and the actual business logic. In this blog post, I’m going to show how to mock up parts of the HttpContext so you can test things like headers, cookies and server variables. From this, you should be able to extend the features to mock up any other part of the context.
The Problem
The HttpContext has been called the largest object on the planet. Unfortunately, HttpContext is difficult to mock and an object called HttpContextBase (inside System.Web) was added to allow developers to mock the context. In order to do this, you’ll need to create a context factor that your application uses. This factory will default to the HttpContext.Current when your program runs normally, but allow your unit tests to override with an HttpContextBase for testing purposes. This also requires you to replace any references to HttpContext.Current with the new context factory object.
The Factory Object
The basic factory object looks like this:
public class HttpContextFactory { private static HttpContextBase m_context; public static HttpContextBase Current { get { if (m_context != null) { return m_context; } if (HttpContext.Current == null) { throw new InvalidOperationException("HttpContext not available"); } return new HttpContextWrapper(HttpContext.Current); } } public static void SetCurrentContext(HttpContextBase context) { m_context = context; } }
Now you refactor all your code to use HttpContextFactory.Current in place of HttpContext.Current. Once this has been accomplished, then you can create unit tests that mock the context. This factory object was obtained from stack overflow (click here).
The Mock Context Object
Next, we’ll need a mock context object. This will be used to contain variables that are pre-assigned to the context before a method is called. Then the mock object can be asserted if the object under test changes any values (like setting a cookie).
Here’s the mock object:
<public class MockHttpContext { public NameValueCollection ServerVariables = new NameValueCollection(); public HttpCookieCollection Cookies = new HttpCookieCollection(); public NameValueCollection HeaderVariables = new NameValueCollection(); public HttpContextBase Context { get { var httpRequest = new Moq.Mock<HttpRequestBase>(); httpRequest.Setup(x => x.ServerVariables.Get(It.IsAny<string>())) .Returns<string>(x => { return ServerVariables[x]; }); httpRequest.SetupGet(x => x.Cookies).Returns(Cookies); httpRequest.Setup(x => x.Headers.Get(It.IsAny<string>())) .Returns<string>(x => { return HeaderVariables[x]; } ); var httpContext = (new Moq.Mock<HttpContextBase>()); httpContext.Setup(x => x.Request).Returns(httpRequest.Object); return httpContext.Object; } } }
There are variables to contain cookies, header variables and server variables. These can be set by a unit test before executing the method under test. The values can also be read after the method has executed to verify expected changes.
Writing a Unit Test
Here’s a basic unit test with server variables:
[TestMethod] public void httpcontext_server_variables() { var tempContext = new MockHttpContext(); tempContext.ServerVariables.Add("REMOTE_ADDR", "127.0.0.1"); tempContext.ServerVariables.Add("HTTP_USER_AGENT", "user agent string here"); tempContext.ServerVariables.Add("HTTP_X_FORWARDED_FOR", "12.13.14.15"); HttpContextFactory.SetCurrentContext(tempContext.Context); //TODO: call http method //TODO: asserts }
That’s all there is to it. Now you’re probably wondering what’s the point? The reason for mocking the context is to get legacy code in a unit testing harness. If your legacy code is already in C# and you are using web forms or MVC, then you can use this technique to unit test any methods called from your web application. The process is to create a unit test with minimal changes to existing code. Then perform your refactoring or rewriting of code while applying the same unit tests. This will help ensure that you are following the original spec of the legacy code.
Where to Get the Code
You can download the sample code from my GitHub account by clicking here. This code was built with Visual Studio 2012.