Friday 24 October 2008

Unit Testing the Enterprise Library #2 - Unit Testing the Caching Application Block

Unit testing the caching application block for my project was a little different from the logging application block.  There was no reason to test my own custom cache manager and for the part of my tests I have assumed that the enterprise library functions as expected and does not require unit testing.  What I must then unit test is that I am correctly consuming the application block into my code.

In my code I have a class that is responsible for storing and retrieving cache objects.  All my objects are stored using a key object with an overridden ToString() method like so.

class CacheKey
{
    /// <summary>
    /// Must be an object that uniquely identifies itself thorugh ToString (like int or string).
    /// </summary>
    public object Key { get; set; }
 
    /// <summary>
    /// The type of the object stored.
    /// </summary>
    public Type ObjectType { get; set; }
 
    public CacheKey()
    {
    }
 
    /// <summary>
    /// Override constructor to pass in the parameters.
    /// </summary>
    /// <param name="key">The key for the object</param>
    /// <param name="objectType">The type of the object</param>
    public CacheKey(object key, Type objectType)
    {
        Key = key;
        ObjectType = objectType;
    }
 
    /// <summary>
    /// This override will cat together the key and the type into a string to reference the object
    /// in the cache uniquely.
    /// </summary>
    /// <returns></returns>
    public override string ToString()
    {
        if (Key == null)
        {
            throw new System.ArgumentNullException("Key");
        }
 
        if (ObjectType == null)
        {
            throw new System.ArgumentNullException("ObjectType");                
        }
 
        if (Key.ToString().Trim().Length == 0)
        {
            throw new System.ArgumentOutOfRangeException("Key");
        }
 
        return (ObjectType.ToString() + "." + Key.ToString());
    }
}

My Caching class will create the key via the index and object type like so:

public void UpdateCache(object key, object toCache, CacheManager manager)
{
    CacheKey cacheKey = new CacheKey(key, toCache.GetType());
 
    // Cache managers are stored against the toString output of the CacheManager enum.
    ICacheManager cacheManager = CacheFactory.GetCacheManager(manager.ToString());
    cacheManager.Add(cacheKey.ToString(), toCache);
}

And retrieve it by object type and key in much the same way.

For unit testing, all I had to do was add some items to the cache and then check the cache directly to ensure that my class had put them where they need to be.  Here is a subset of my test cases.  Firstly to test the update of data in the cache:

// Create an object to cache.
string cacheObject = "cached string";
string identifier = "1";
EntLibCache cache = new EntLibCache();
 
// Test for the default cache using multiple different objects with specific Ids.
cache.UpdateCache(identifier, cacheObject, CacheManager.Default);
string defaultCacheManager = CacheManager.Default.ToString();
ICacheManager cacheManager = CacheFactory.GetCacheManager(defaultCacheManager);
CacheKey cacheKey = new CacheKey(identifier, cacheObject.GetType());
string returnFromCache = (string)cacheManager.GetData(cacheKey.ToString());
Assert.AreEqual(cacheObject, returnFromCache);

And then test the retrieval of data from the cache:

EntLibCache target = new EntLibCache();
 
// Add a bunch of objects to the cache.
int key1 = 1;
string value1 = "one";
int key2 = 1;
int value2 = 1234;
int key3 = 3;
string value3 = "three";
 
// Put the values in the cache.
target.UpdateCache(key1, value1, CacheManager.Default);
target.UpdateCache(key2, value2, CacheManager.Default);
target.UpdateCache(key3, value3, CacheManager.User);
 
Assert.AreEqual(value1, (string)target.GetObject(key1, value1.GetType(), CacheManager.Default));
Assert.AreEqual(value2, (int)target.GetObject(key2, value2.GetType(), CacheManager.Default));
Assert.AreEqual(value3, (string)target.GetObject(key3, value3.GetType(), CacheManager.User));

It’s that simple.  Not all of my test cases are here, no point posting all my code, but they follow these lines fairly well.  In my case I use an enum to specify the cache manager and use the ToString method to turn it into the string representation that is required by the cache manager, thus keeping strong typing in my code.

You will have to be sure to edit the app.config of your test class to add the cache managers you need or your tests will fall over.

No comments: