Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)

like image 323
Lars Holdgaard Avatar asked Aug 04 '13 12:08

Lars Holdgaard


People also ask

What are best practices for unit testing methods that use cache heavily?

If you want true Unit Tests, then you have to mock the cache: write a mock object that implements the same interface as the cache, but instead of being a cache, it keeps track of the calls it receives, and always returns what the real cache should be returning according to the test case.

What is cache testing?

Caching has been established as a vital mechanism for providing high scalability in modern distributed software systems. Each conventional web browser contains a caching systems which stores and reuses HTTP responses in order to reduce data traffic and communication latency.

How do I run a MSTest unit test?

To run MSTest unit tests, specify the full path to the MSTest executable (mstest.exe) in the Unit Testing Options dialog. To call this dialog directly from the editor, right-click somewhere in the editor and then click Options.


2 Answers

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);
like image 189
Honza Brestan Avatar answered Oct 16 '22 09:10

Honza Brestan


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.

like image 4
Joe Avatar answered Oct 16 '22 09:10

Joe