Unit testing the cache in Episerver Community
One irritating problem that can occur more than you like when you are working with EPiServer community is that the cache is not updated when it is supposed to. In my latest project we found a way to test the cache on community modules that you have developed yourself. In this example I’m going to use Rhino Mocks
Prepare your module
If you follow the standard pattern EPiServer uses on all there modules you will end up having a Factory that handles all the database queries, you need to make all methods used by the manager virtual and non static to be able to fake the methods on the factory. The standard pattern can look something like this, written on a whiteboard in my office:
Here is an example of the factory
public class EntityDataFactory : FrameworkFactoryBase
{
public virtual Entity GetEntity(int id)
{
//Some code
}
}
When that's done you need to define a static factory member in your Manager to access the Factory its important to make it setable so you easly can define your own datafactory. An example:
private static EntityDataFactory _dataFactoryInstance = new EntityDataFactory();
public static EntityDataFactory DataFactoryInstance
{
get
{
return _dataFactoryInstance;
}
set
{
_dataFactoryInstance = value;
_getEntityDelegate = _dataFactoryInstance.GetEntity;
_getEntityForDelegate = _dataFactoryInstance.GetEntityFor;
}
}
Enable the and setting up the cache
The asp.net cache needs httpcontext to be inizialised to work so we need to initialise it.
public static void InitCurrentHttpContext()
{
TextWriter tw = new StringWriter();
HttpWorkerRequest wr = new SimpleWorkerRequest(
"/",
"C:\\",
"default.aspx", "", tw);
HttpContext.Current = new HttpContext(wr);
}
public static void ClearCurrentContextCache()
{
if (HttpContext.Current != null)
{
foreach (DictionaryEntry cache in HttpContext.Current.Cache)
HttpContext.Current.Cache.Remove(cache.Key.ToString());
return;
}
}
Now when we access the cache it will not be configured and it will give us errors. We can try to start the whole community framework but that is time consuming task and we want our test to be as fast as possible. I used the Reflector to find which variables to be set and used reflection to sett the correct values.
public static void InitCache(HttpContext context)
{
Type type = typeof(CacheHandler);
FieldInfo infoCache = type.GetField("m_cache", BindingFlags.NonPublic | BindingFlags.Static);
infoCache.SetValue(null, HttpContext.Current.Cache);
FieldInfo info = type.GetField("m_cacheExpirationProvider", BindingFlags.NonPublic | BindingFlags.Static);
CacheExpirationProvider provider = new CacheExpirationProvider();
provider.Initialize(new NameValueCollection());
info.SetValue(null, provider);
}
public static void InitCacheAndHttpContextWithAClearCache()
{
if (HttpContext.Current != null)
ClearCurrentContextCache();
else
InitCurrentHttpContext();
InitCache(HttpContext.Current);
}
Testing and faking(mocking and stubbing)
Now all that's left is to create the test and mock the data factory. Our goal here is too see how many times we have accessed a method on the datafactory to determine if the cache is used or not. First we need to create a mock off the factory:
public static EntityDataFactory GetFakeEntityDataFactoryAndSetUpCache()
{
EpiCommunityCacheSetup.InitCacheAndHttpContextWithAClearCache();
MockRepository fakes = new MockRepository();
EntityDataFactory factory = fakes.Stub<EntityDataFactory>();
factory.Stub(x => x.GetEntity(FAKE_ENTITY_ID)).Return(FAKE_ENTITY);
EntityHandler.DataFactoryInstance = factory;
return factory;
}
Then we build a test using the AAA(Arrange, Act, Assert) principle. Here I'm trying to test that the get method on the datafactory only get called once and the second time the cache should take the hit.
[Fact]
public void GivenNoPreviousGetBy_WhenGetIsCalled_ThenDataFactoryCalled()
{
EntityDataFactory fakeFactory = FakeFactory.GetFakeEntityDataFactoryAndSetUpCache();
EntityHandler.GetEntityFor(FakeFactory.FAKE_REMOTESITENAME, FakeFactory.FAKE__ID);
EntityHandler.GetEntityFor(FakeFactory.FAKE_REMOTESITENAME, FakeFactory.FAKE__ID);
fakeFactory.AssertWasCalled(factory => factory.GetEntityFor(FakeFactory.FAKE_REMOTESITENAME, FakeFactory.FAKE__ID));
}
Hopes this helps you make your EpiServerCommunity modules a bit more testable.