views:

5499

answers:

10

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?

+2  A: 

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...

Antony Perkov
+5  A: 

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.

Steve Wright
You can mock almost anything with HttpContextBase, but the Cache property is not among them. HttpContextBase.Cache is of type System.Web.Caching.Cache which is sealed, unusable in unit tests and not mockable...
chris166
+10  A: 

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!

Orion Edwards
A: 

All of these programming questions ask for an interface based programming model, in which you implement the interface twice. One for the real code and one for the mockup.

Instantiation is then the next issue. There are several design patterns that can be used for that. See for example the famous GangOfFour Creational patterns (GOF) or the Dependency Injection patterns.

ASP.Net MVC is in fact using this interface-based approach and is therefore much more suitable for unit testing.

Rine
+5  A: 

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);
    }
}
Brian Surowiec
A: 

You can use HttpContextBase class in System.Web.Abstractions.dll. It's a new dll in .NET 3.5.

You can find an example how to use in the link below.

http://vkreynin.wordpress.com/2009/03/23/stub-htttpcontext/

Vadim
A: 

As everyone said here, there's a problem with HTTPContext, currently Typemock is the only framework that can fake it directly without any wrappers or abstractions.

A: 

This maybe up your street ... Phil Haack shows off, with the help of Rhino mocks, how to mock httpcontext in asp mvc but I'd imagine can be applied to webforms.

Clicky!!

Hope this helps.

WestDiscGolf
A: 

The cache object is difficult to mock because it's a sealed area of the .NET framework. I typically get around this by building a cache wrapper class that accepts a cache manager object. For testing, I use a mock cache manager; for production I use a cache manager that actually accesses HttpRuntime.Cache.

Basically, I abstract away the cache myself.

Chris
A: 
HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
Shawn Miller