Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit test HttpContext.Current.Cache or other server-side methods in C#?

When creating a unit test for a class that uses the HttpContext.Current.Cache class, I get an error when using NUnit. The functionality is basic - check if an item is in the cache, and if not, create it and put it in:

if (HttpContext.Current.Cache["Some_Key"] == null) {
    myObject = new Object();
    HttpContext.Current.Cache.Insert("Some_Key", myObject);
}
else {
    myObject = HttpContext.Current.Cache.Get("Some_Key");
}

When calling this from a unit test, it fails with at NullReferenceException when encountering the first Cache line. In Java, I would use Cactus to test server-side code. Is there a similar tool I can use for C# code? This SO question mentions mock frameworks - is this the only way I can test these methods? Is there a similar tool to run tests for C#?

Also, I don't check if the Cache is null as I don't want to write code specifically for the unit test and assume it will always be valid when running on a server. Is this valid, or should I add null checks around the cache?

like image 452
Tai Squared Avatar asked Jan 28 '09 22:01

Tai Squared


6 Answers

The way to do this is to avoid direct use of the HttpContext or other similar classes, and substitute them with mocks. After all, you're not trying to test that the HttpContext functions properly (that's microsoft's job), you're just trying to test that the methods got called when they should have.

Steps (In case you just want to know the technique without digging through loads of blogs):

  1. Create an interface which describes the methods you want to use in your caching (probably things like GetItem, SetItem, ExpireItem). Call it ICache or whatever you like

  2. Create a class which implements that interface, and passes methods through to the real HttpContext

  3. Create a class which implements the same interface, and just acts like a mock cache. It can use a Dictionary or something if you care about saving objects

  4. Change your original code so it doesn't use the HttpContext at all, and instead only ever uses an ICache. The code will then need to get an instance of the ICache - you can either pass an instance in your classes constructor (this is all that dependency injection really is), or stick it in some global variable.

  5. In your production app, set the ICache to be your real HttpContext-Backed-Cache, and in your unit tests, set the ICache to be the mock cache.

  6. Profit!

like image 136
Orion Edwards Avatar answered Nov 17 '22 18:11

Orion Edwards


I agree with the others that using an interface would be the best option but sometimes it’s just not feasible to change an existing system around. Here’s some code that I just mashed together from one of my projects that should give you the results you’re looking for. It’s the farthest thing from pretty or a great solution but if you really can’t change your code around then it should get the job done.

using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Web;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;

[TestFixture]
public class HttpContextCreation
{
    [Test]
    public void TestCache()
    {
        var context = CreateHttpContext("index.aspx", "http://tempuri.org/index.aspx", null);
        var result = RunInstanceMethod(Thread.CurrentThread, "GetIllogicalCallContext", new object[] { });
        SetPrivateInstanceFieldValue(result, "m_HostContext", context);

        Assert.That(HttpContext.Current.Cache["val"], Is.Null);

        HttpContext.Current.Cache["val"] = "testValue";
        Assert.That(HttpContext.Current.Cache["val"], Is.EqualTo("testValue"));
    }

    private static HttpContext CreateHttpContext(string fileName, string url, string queryString)
    {
        var sb = new StringBuilder();
        var sw = new StringWriter(sb);
        var hres = new HttpResponse(sw);
        var hreq = new HttpRequest(fileName, url, queryString);
        var httpc = new HttpContext(hreq, hres);
        return httpc;
    }

    private static object RunInstanceMethod(object source, string method, object[] objParams)
    {
        var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
        var type = source.GetType();
        var m = type.GetMethod(method, flags);
        if (m == null)
        {
            throw new ArgumentException(string.Format("There is no method '{0}' for type '{1}'.", method, type));
        }

        var objRet = m.Invoke(source, objParams);
        return objRet;
    }

    public static void SetPrivateInstanceFieldValue(object source, string memberName, object value)
    {
        var field = source.GetType().GetField(memberName, BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
        if (field == null)
        {
            throw new ArgumentException(string.Format("Could not find the private instance field '{0}'", memberName));
        }

        field.SetValue(source, value);
    }
}
like image 40
Brian Surowiec Avatar answered Nov 17 '22 17:11

Brian Surowiec


HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
like image 38
Shawn Miller Avatar answered Nov 17 '22 19:11

Shawn Miller


If you're using .NET 3.5, you can use System.Web.Abstractions in your application.

Justin Etheredge has a great post on how to mock HttpContext (which contains the cache class).

From Justin's example, I pass an HttpContextBase to my controllers using the HttpContextFactory.GetHttpContext. When mocking them, I just build a Mock to make calls to the cache object.

like image 6
Steve Wright Avatar answered Nov 17 '22 19:11

Steve Wright


There is a newer approach to help deal specifically with Cache in unit tests.

I would recommend using Microsoft's new MemoryCache.Default approach. You will need to use .NET Framework 4.0 or later and include a reference to System.Runtime.Caching.

See article here --> http://msdn.microsoft.com/en-us/library/dd997357(v=vs.100).aspx

MemoryCache.Default works for both web and non-web applications. So the idea is you update your webapp to remove references to HttpContext.Current.Cache and replace them with references to MemoryCache.Default. Later, when you run decide to Unit Test these same methods, the cache object is still available and won't be null. (Because it is not reliant on an HttpContext.)

This way you don't even necessarily need to mock the cache component.

like image 5
ClearCloud8 Avatar answered Nov 17 '22 19:11

ClearCloud8


General consensus seems to be that driving anything HttpContext related from within a unit test is a total nightmare, and should be avoided if possible.

I think you're on the right path regarding mocking. I like RhinoMocks (http://ayende.com/projects/rhino-mocks.aspx).

I've read some good things about MoQ too (http://code.google.com/p/moq), although I've not tried it yet.

If you really want to write unit testable web UIs in C#, the way people seem to be heading is to use the MVC framework (http://www.asp.net/mvc) rather than WebForms...

like image 2
Antony Perkov Avatar answered Nov 17 '22 19:11

Antony Perkov