Unit Testable HttpModule and HttpHandler

With the new System.Web.Abstraction namespace we can now easily write unit testable HttpModule and HttpHandler. In this post, I will show you how to write unit testable HttpModule and HttpHandler.

Prior the release of System.Web.Abstraction the problem with unit testing of these web infrastructural items is HttpContext, it is sealed, no way to mock it with Rhino or Moq. The only option is to create the wrapper objects of these non-mockable objects but it has a very long tail like HttpRequest, HttpResponse, HttpSessionState, HttpServerUtility etc etc. And this is the exact thing that the System.Web.Abstraction provides, all wrappers around the non-mockable objects of HttpContext. But still, both the HttpModule and HttpHandler depends upon the original HttpContext which I hope will change in ASP.NET 4.0. till then we need to create a super layer for making it Unit Testable.

HttpModule

public abstract class BaseHttpModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += (sender, e) => OnBeginRequest(new HttpContextWrapper(((HttpApplication) sender).Context));
        context.Error += (sender, e) => OnError(new HttpContextWrapper(((HttpApplication) sender).Context));
        context.EndRequest += (sender, e) => OnEndRequest(new HttpContextWrapper(((HttpApplication) sender).Context));
    }

    public void Dispose()
    {
    }

    public virtual void OnBeginRequest(HttpContextBase context)
    {
    }

    public virtual void OnError(HttpContextBase context)
    {
    }

    public virtual void OnEndRequest(HttpContextBase context)
    {
    }
}

You can hook the other events if it is required for your application. Now lets create a basic HttpModule which will remove the www from the url:

public class RemoveWwwModule : BaseHttpModule
{
    public override void OnBeginRequest(HttpContextBase context)
    {
        const string Prefix = "http://www.";

        string url = context.Request.Url.ToString();

        bool startsWith3W = url.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase);

        if (startsWith3W)
        {
            string newUrl = "http://" + url.Substring(Prefix.Length);

            HttpResponseBase response = context.Response;

            response.StatusCode = (int) HttpStatusCode.MovedPermanently;
            response.Status = "301 Moved Permanently";
            response.RedirectLocation = newUrl;
            response.SuppressContent = true;
            response.End();
        }
    }
}

Unit Test:

public class RemoveWwwModuleFixture
{
    private readonly Mock<HttpContextBase> _httpContext;
    private readonly Mock<HttpRequestBase> _httpRequest;
    private readonly Mock<HttpResponseBase> _httpResponse;

    public RemoveWwwModuleFixture()
    {
        _httpContext = new Mock<HttpContextBase>();
        _httpRequest = new Mock<HttpRequestBase>();
        _httpResponse = new Mock<HttpResponseBase>();

        _httpContext.SetupGet(context => context.Request).Returns(_httpRequest.Object);
        _httpContext.SetupGet(context => context.Response).Returns(_httpResponse.Object);
    }

    [Fact]
    public void OnBeginRequest_Should_Redirect_When_Requesting_Url_Which_Starts_With_WWW()
    {
        _httpRequest.SetupGet(request => request.Url).Returns(new Uri("http://www.mysite.com/"));

        _httpResponse.SetupSet(response => response.StatusCode = (int) HttpStatusCode.MovedPermanently);
        _httpResponse.SetupSet(response => response.Status = "301 Moved Permanently");
        _httpResponse.SetupSet(response => response.RedirectLocation = "http://mysite.com/");
        _httpResponse.SetupSet(response => response.SuppressContent = true);
        _httpResponse.Setup(response => response.End());

        var module = new RemoveWwwModule();

        module.OnBeginRequest(_httpContext.Object);

        _httpResponse.VerifyAll();
    }
}

HttpHandler

public abstract class BaseHttpHandler : IHttpHandler
{
    public virtual bool IsReusable
    {
        get
        {
            return false;
        }
    }

    public void ProcessRequest(HttpContext context)
    {
        ProcessRequest(new HttpContextWrapper(context));
    }

    public abstract void ProcessRequest(HttpContextBase context);
}

Now lets create an HttpHandler which combines few JavaScript files, I will be skipping the combine part as it is not relevant to this post.

public class JavaScriptHandler : BaseHttpHandler
{
    public override void ProcessRequest(HttpContextBase context)
    {
        string content = GetContent();
        HttpResponseBase response = context.Response;

        response.ContentType = "application/x-javascript";
        response.Write(content);
    }

    private string GetContent()
    {
        return "YOUR JAVA SCRIPT FILES CONTENT";
    }
}

Unit Test:

public class JavaScriptHandlerFixture : IDisposable
{
    private readonly Mock<HttpContextBase> _httpContext;
    private readonly Mock<HttpResponseBase> _httpResponse;

    private readonly JavaScriptHandler _handler;

    public JavaScriptHandlerFixture()
    {
        _httpContext = new Mock<HttpContextBase>();
        _httpResponse = new Mock<HttpResponseBase>();

        _httpContext.SetupGet(context => context.Response).Returns(_httpResponse.Object);

        _handler = new JavaScriptHandler();
    }

    public void Dispose()
    {
        _httpResponse.VerifyAll();
    }

    [Fact]
    public void ProcessRequest_Should_Set_Correct_ContentType()
    {
        _httpResponse.SetupSet(response => response.ContentType = "application/x-javascript");

        _handler.ProcessRequest(_httpContext.Object);
    }

    [Fact]
    public void ProcessRequest_Should_Write_Content()
    {
        _httpResponse.Setup(response => response.Write(It.IsAny<string>()));

        _handler.ProcessRequest(_httpContext.Object);
    }
}

If you want see more Unit Testable HttpModule and HttpHandler, go checkout the source of KiGG.

Shout it

2 Comments

Comments have been disabled for this content.