Question

I've been going through the process of cleaning up our controller code to make each action as testable. Generally speaking, this hasn't been too difficult--where we have opportunity to use a fixed object, like say FormsAuthentication, we generally introduce some form of wrapper as appropriate and be on our merry way.

For reasons not particularly germaine to this conversation, when it came to dealing with usage of HttpContext, we decided to use the newly created HttpContextWrapper class rather than inventing something homegrown. One thing we did introduce was the ability to swap in a HttpContextWrapper (like say, for unit testing). This was wholly inspired by the way Oren Eini handles unit testing with DateTimes (see article, a pattern we also use)

public static class FooHttpContext
{
    public static Func<HttpContextWrapper> Current = () 
         => new HttpContextWrapper(HttpContext.Current);

    public static void Reset()
    {
        Current = () => new HttpContextWrapper(HttpContext.Current);
    }
}

Nothing particularly fancy. And it works just fine in our controller code. The kicker came when we go to write unit tests. We're using Moq as our mocking framework, but alas

var context = new Mock<HttpContextWrapper>() 

breaks since HttpContextWrapper doesn't have a parameterless ctor. And what does it take as a ctor parameter? A HttpContext object. So I find myself in a catch 22.

I'm using the prescribed way to decouple HttpContext--but I can't mock a value in because the original HttpContext object was sealed and therefore difficult to test. I can map HttpContextBase, which both derive from--but that doesn't really get me what I'm after. Am I just missing the point somewhere with regard to HttpContextWrapper?

Edit to clarify intent

We found ways to solve the problem--but I guess the ultimate question we're walking away with is what value HttpContextWrapper brings to the table? I don't doubt somewhere someone totally had an a-ha! moment with it, but it just doesn't come to me. Most postings I see here discuss it in terms of testability--but my own experience has led me to believe that it didn't bring much in that context. Unless we're doing it wrong. (Wholly possible).

Was it helpful?

Solution

You should be using the abstract HttpContextBase which is much easier to mock instead of HttpContextWrapper.

public static Func<HttpContextBase> Current = 
    () => new HttpContextWrapper(HttpContext.Current);

And in your unit test:

SomeClass.Current = MockHttpContextBase(); // Sorry I don't know the syntax for Moq

OTHER TIPS

This blog post explains it pretty well:

http://splinter.com.au/httpcontext-vs-httpcontextbase-vs-httpcontext

The point is that 'vintage' HttpContext does not implement HttpContextBase, and isn't virtual, and therefore cannot be Mocked. HttpContextBase was introduced in 3.5 as a mockable alternative. But there's still the problem that vintage HttpContext doesn't implement HttpContextBase.

So HttpContextWrapper is a handy wrapper class (or 'kludge') that does implement HttpContextBase, and can be used when injecting a 'real' HttpContext using IOC, usually with a factory method like this: () => new HttpContextWrapper(HttpContext.Current)

One real world example other than testing. Besides mocking I stumbled upon a peculiar issue that the wrapper class really helped me solve. We have an application in Azure and we have control only on the application. It sits behind a reverse proxy which changes the host header of the incoming requests and sends the original host in a custom header. The application relies on the host header in order to build dynamic links, validate redirects etc. so we needed a way to substitute the host that was set in the HttpContext.HttpRequests.Url property. Since we exposed the HttpContext only as HttpContextBaase using the wrapper everywhere in the app we were able to create a class that inherits HttpContextWrapper and overrides the Request and then returns object that inherits from the RequestWrapper and overrides the Url property. So in the end the application replaced the host in the url that ASP.NET used for the context with the custom host from the custom header the reverse proxy has set. There was no other way from within the app to do that other than manually searching through the code where HttpContext.Request.Url is used and applying fix.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top