Make your manual bot handling with SimpleHoneypot.MVC unit testable by breaking the dependency on the HttpContext class

Make your manual bot handling with SimpleHoneypot.MVC unit testable

I asked a coworker of mine yesterday where I should start with these blog posts. Should I walk people through creating a project and adding each class or should I just expect people to have this knowledge ahead of time? He told me that he had seen other blogs that just gave a bit of a disclaimer about who these posts are targeting. I have to agree that a disclaimer for the target audience would be the best way of starting.

Here is my disclaimer for this post: I’m going to walk you through how to make your actions unit testable that manually handle bots with the NuGet package SimpleHoneypot.MVC. I’m assuming you’re all familiar with captcha functionality that prevents bots from spamming your publicly available forms and that you have worked with the SimpleHoneypot.MVC NuGet package.

The appeal of a honeypot implementation is that you no longer require your users to fill out captcha like inputs to verify that they’re not bots. Instead, you simply hide an input on your form with a css class, and when that input has a value associated with it in the request you can assume it was a bot. For more information on the honeypot technique I recommend you read Phil Haack’s blog post on it here.

I recently used SimpleHoneypot.MVC on a post action that generated an email to one of our customers. We wanted to assure them that they wouldn’t be spammed with emails by bots. A coworker of mine quickly realized that the manual call to validate the honeypot field was tied to the HttpContext class which made life difficult to unit test the action. Here is a look at the SimpleHoneypot.MVC source code for the HttpRequestBase extension method HoneyPotFaild and I did spell the method name correctly.

public static class HttpRequestBaseExtensions {

    public static bool HoneypotFaild(this HttpRequestBase request) {
        Check.Argument.IsNotNull(request, "context");

        if (HttpContext.Current.Items[Honeypot.HttpContextKey] == null)
            throw new NullReferenceException("HttpContext.Items must have entry for Honeypot.HttpContextKey. Ensure your action has HoneypotAttribute");
        return (bool)HttpContext.Current.Items[Honeypot.HttpContextKey];
    }
}

In order to unit test your action that would call this method we would have to create a new instance of the HttpContext class which is a major challenge and is unnecessary. Instead we want the manual method to use the HttpContextBase class that is easy to mock. The quick and easy way of doing this is by having the Honeypot validation check be tied to the HttpContextBase since we just need to check the Items IDictionary property for the honeypot key. The following is the class I created to make the HttpContextBase extension method. using System; using System.Web; using SimpleHoneypot.Core;

namespace SimpleHoneypot.Extensions.Helpers
{
    public static class HoneypotControllerExtensions
    {
        /// <summary>
        /// This method is a unit testable version of SimpleHoneypot.MVC HasHoneypotFaild method which
        /// was tied to the HttpContext class.
        /// </summary>
        /// <param name="httpContextBase"></param>
        /// <returns></returns>
        public static bool HasHoneypotFailed(this HttpContextBase httpContextBase)
        {
            if (httpContextBase.Items[Honeypot.HttpContextKey] == null)
            {
                throw new NullReferenceException("HttpContext.Items must have entry for Honeypot.HttpContextKey. Ensure your action has HoneypotAttribute");
            }

            return (bool)httpContextBase.Items[Honeypot.HttpContextKey];
        }
    }
}

I am going to reference the extension method that I wrote for the HttpContextBase class inside of my action that has the Honeypot validation. Just a disclaimer: with this action I would have a Model created for the individual view that the action returns, but for the sake of showing the bare minimum this is not the case.

[HttpPost]
[Honeypot(true)]
public ActionResult Index(string fullName, string emailAddress)
{
    if (HttpContext.HasHoneypotFailed())
    {
        throw new HttpException(500, "Honeypot validation failed from a potential bot trying to spam.");
    }

    return View();
}

Our preference at work is to throw an HttpException that will be caught by our logging mechanism. This allows us to see when we have potential bots trying to spam our forms.

Let’s take a look at our unit test written to verify the HttpException has been thrown when honeypot has failed. We’ll need to setup our Controller so that we can fake honeypot failing. I’m using Moq to help me mock the HttpContextBase class which will need to have an entry in the Items property for the Honeypot:IsBot item key with a value of true. The reason that we have to create the controller context instead of directly mocking the Controller.HttpContext property is that it’s read-only and therefore we cannot simply assign our mock to it.

[TestMethod]
[ExpectedException(typeof(HttpException))]
public void Index_PostAndHoneypotValidationFailed_ThrowsHttpException()
{
    // arrange
    var testController = new TestController();
    var context = new Mock<HttpContextBase>();
    context.SetupGet(c => c.Items).Returns(new Dictionary<string, object>());
    testController.ControllerContext = new ControllerContext(new RequestContext(context.Object, new RouteData()), testController);
    testController.HttpContext.Items["Honeypot:IsBot"] = true;

    // act
    testController.Index("fullname", "test@test.com");

    // assert

}

The test that you see was written for MSTest and should be readable enough for those of you using NUnit. The full source code for this project is out on my BitBucket account here. Let me know if you have any issues and happy coding!

asp.netmvcSimpleHoneypot.MVCunit testing
Posted by: Justin Michaels
Last revised: 05 May, 2012 06:43 PM

Comments

No comments yet. Be the first!

blog comments powered by Disqus