Static classes and methods are a pain to unit test
Back in days of WebForms, HttpContext was the one object to rule them all. It’s got session state, request and response, cache, errors, server variables and so much more for developers to inspect and play with. HttpContext.Current was by far the easiest way to tap into all of this. But guess what? Static member invocation does not make mocking it out easy.
MVC controllers are much more unit-test friendly
Although technically HttpContext hasn’t gone anywhere with the coming of MVC, it’s been neatly wrapped into a HttpContextWrapper and exposed as controller instance .Context property. Just mock it out and everything will be fine.
End of story? Well may be
If you wanted to completely abstract from all HTTP specifics and happen to not need and utility methods that come with it – you’re sweet.
If, however, for some reason you feel like relying on some utility methods to reduce amount of non-productive mocking, try this trick:
public class MockHttpContext: IDisposable {
public HttpContext Current {
get;
set;
}
private AppDomain CurrentDomain {
get;
}
public MockHttpContext(string url, string query = null, IPrincipal principal = null) {
CurrentDomain = Thread.GetDomain();
var path = CurrentDomain.BaseDirectory;
var virtualDir = "/";
CurrentDomain.SetData(".appDomain", "*");
CurrentDomain.SetData(".appPath", path);
CurrentDomain.SetData(".appId", "testId");
CurrentDomain.SetData(".appVPath", virtualDir);
CurrentDomain.SetData(".hostingVirtualPath", virtualDir);
CurrentDomain.SetData(".hostingInstallDir", HttpRuntime.AspInstallDirectory);
CurrentDomain.SetData(".domainId", CurrentDomain.Id);
// User is logged out by default
principal = principal ?? new GenericPrincipal(
new GenericIdentity(string.Empty),
new string[0]
);
Current = new HttpContext(
new HttpRequest("", url, query),
new HttpResponse(new StringWriter())
) {
User = principal
};
HttpContext.Current = Current;
}
public void Dispose() {
//clean up
HttpContext.Current = null;
}
}
First it looks very similar to this implementation taken from SO (well, this is where it’s been taken off to begin with). But then what’s up with all these CurrentDomain.SetData
calls? This allows us to mock paths and transition between relative and absolute urls as if we were hosted somewhere.
Consider the code:
public static string ToAbsoluteUrl(this string relativeUrl) {
if (string.IsNullOrEmpty(relativeUrl)) return relativeUrl;
if (HttpContext.Current == null) return relativeUrl;
if (relativeUrl.StartsWith("/")) relativeUrl = relativeUrl.Insert(0, "~");
if (!relativeUrl.StartsWith("~/")) relativeUrl = relativeUrl.Insert(0, "~/");
var url = HttpContext.Current.Request.Url;
var port = !url.IsDefaultPort ? ":" + url.Port : string.Empty;
return $ "{url.Scheme}://{url.Host}{port}{VirtualPathUtility.ToAbsolute(relativeUrl)}"; // and this is where the magic happens. Now static invocation of VirtualPathUtility does not fail with NullReferenceException anymore!
}
The code outside makes afew assumptions regarding the environment being mocked, but it should be a trivial task to introduce more parameters/settings and mock everything away.