Lars Holdgaard
Lars Holdgaard

Reputation: 9966

How to unit test a service which uses caching?

I have a service layer, which has a range of methods. These methods have implemented caching, like the following:

string key = "GetCategories";
if (CacheHandler.IsCachingEnabled() && !CacheHandler.ContainsKey(key))
{
    var categories = RequestHelper.MakeRequest("get_category_index")["categories"];
    var converted = categories.ToObject<List<Category>>();
    CacheHandler.InsertToCache(key,converted);
    return converted;
}
return CacheHandler.GetCache(key) as List<Category>;

Now, problem is I also want to make a unit test, like the following:

[TestMethod]
public void GetCategories()
{
    IContentService contentService = new ContentService();
    var resp = contentService.GetCategories();
    Assert.IsNotNull(resp,"Should not be null");
}

Problem is, that the HttpContext.Current inside my CacheHandler is null during the unit test (obviously).

What is the easiest way to fix this?

(Please be as specific as possible because I haven't done a lot of unit testing before)

Upvotes: 6

Views: 16563

Answers (4)

Torbj&#246;rn Kalin
Torbj&#246;rn Kalin

Reputation: 1986

Separate the cache into its own class, working like a proxy.

public interface IContentService
{
    Categories GetCategories();
}

public class CachingContentService : IContentService
{
    private readonly IContentService _inner;

    public CachingContentSerice(IContentService _inner)
    {
        _inner = inner;
    }

    public Categories GetCategories()
    {
        string key = "GetCategories";
        if (!CacheHandler.ContainsKey(key))
        {
            Catogories categories = _inner.GetCategories();
            CacheHandler.InsertToCache(key, categories);
        }
        return CacheHandler.GetCache(key);
    }
}

public class ContentSerice : IContentService
{
    public Categories GetCategories()
    {
        return RequestHelper.MakeRequest("get_category_index")["categories"];
    }
}

To enable caching, decorate the real ContentService with the cache:

var service = new CachingContentService(new ContentService());

To test the cache, create the CachingContentService with a test double as a constructor parameter. Use the test double to verify the cache: call it once and it should call the service behind. Call it twice and it should not call the service behind.

Upvotes: 3

to StackOverflow
to StackOverflow

Reputation: 124716

Dependency Injection as recommended in Honza Brestan's answer is certainly a valid solution, and maybe the best solution - especially if you might want to use something other than the ASP.NET Cache in the future.

However I should point out that you can use the ASP.NET Cache without needing an HttpContext. Instead of referencing it as HttpContext.Current.Cache, you can use the static property HttpRuntime.Cache.

This will enable you to use the Cache outside the context of an HTTP request, such as in a unit test or in a background worker thread. In fact I'd generally recommend using HttpRuntime.Cache for data caching in the business tier to avoid taking the dependency on the existence of an HttpContext.

Upvotes: 4

Honza Brestan
Honza Brestan

Reputation: 10947

This screams dependency injection. The main problem I see is that you access the CacheHandler statically, so in a unit test, you:
a) cannot test the service without "testing" the CacheHandler as well
b) cannot supply any other CacheHandler to the service, for example a mocked one

If that's possible in your case, I'd either refactor or at least wrap the CacheHandler so that the service accesses an instance of it. In a unit test, you can then supply the service with a "fake" CacheHandler, that would not access HttpContext and also could give you a very fine control over the test itself (e.g. you can test what happens when an item is cached vs. when it isn't in two absolutely independent unit tests)

For the mocking part, I suppose it's easiest to create an interface and then use some automocking/proxy-generation framework designed for testing, for example Rhino Mocks (but there are many more, it just happens that I'm using this one and am very happy with it :)). Another approach (easier for a beginner, but more cumbersome in an actual development) would be simply to design the CacheHandler (or its wrapper) so that you can inherit from it and override the behaviour yourself.

Finally for the injection itself, I have found out a handy "pattern", which takes advantage of C# default method arguments and the standard constructor injection. The service constructor would look like:

public ContentService(ICacheHandler cacheHandler = null)
{
    // Suppose I have a field of type ICacheHandler to store the handler
    _cacheHandler = cacheHandler ?? new CacheHandler(...);
}

So in the application itself, I can call the constructor without parameters (or let frameworks construct the service, if it's ASP.NET handler, WCF service or some other kind of class) and in unit tests, I can supply whatever is implementing the said interface.

In case of Rhino Mocks, it can look like this:

var mockCacheHandler = MockRepository.GenerateMock<ICacheHandler>();
// Here I can mock/stub methods and properties, set expectations etc...
var sut = new ContentService(mockCacheHandler);

Upvotes: 8

jfrankcarr
jfrankcarr

Reputation: 481

As a best practice, you only want to test one thing during a test and what you're describing has multiple steps. Therefore, it would be best to construct your test of your "RequestHelper.MakeRequest" and other routines so that they run their tests separately from the caching scenario. Testing them separately will let you know if their are problems in these routines or in the caching. You can integrate them later to test them as a group.

To test the caching separately, you can create a mock object to create the HttpContext with the properties that you need. Here are some previous answers that should help you put this together:

How do I mock the HttpContext in ASP.NET MVC using Moq?

Mock HttpContext.Current in Test Init Method

Upvotes: 1

Related Questions