NHibernate, Caching and tests, Oh My!

time to read 3 min | 534 words

I just spent several hours trying to figure out why enabling NHibernate second level caching caused a lot of my tests to fail, seemingly randomly.

The initial problem was obvious, I was trying to be efficient, and I used a stored procedure (yes, I do use them ) to delete all the database and restore it to a known state. With caching, this meant that the cache and the database were in an inconsistent state. I need to restore my database to a consistent state because I need my tests to be repeatable.

Yes, I am familiar with all the argument against unit tests hitting the database, and yes I do feel the pain. I’m going to do something about it soon, but for now, I need to work with what I have got. My problem was compounded by the fact that large parts of the database are read only, so I wanted to keep them in the cache for the tests.

I quickly wrote this function, which was called before each test was run:

private void ClearCache()

{

ISessionFactory factory = Context. SessionFactory;

ICollection types = factory.GetAllClassMetadata().Keys;

foreach (Type type in types)

{

if (typeof(NotChanging) == type)

continue;

factory.Evict(type);

 

}

}

 

And I thought I was through with it, but apparently not. I still got seemingly random tests failures, I couldn’t reproduce them most of the time, and when I did, they made me question whatever the test itself had a bug. Eventually I realized that I was being stupid.

The reason for that is that NHibernate doesn’t really have a single second level cache. It actually has three. Well, not really three, but it is logically segmented to three caches.

  1. The first cache, which I cleared effectively, was the object cache. This holds the values of the cached entities.
  2. The second cache, which I forgot about, is the collections cache. This holds the association between the entities.
  3. The third cache, which I am not using, is the query cache. It holds the results of HQL queries.

There are also cache regions, but I won’t get into that now. What I need to do was simply add this to my ClearCache() method, and everything turned out just fine:

foreach (string role in factory.GetAllCollectionMetadata().Keys)

{

if(!role.Contains("NotChangin"))

factory.EvictCollection(role);

}

 

I need the caching support to hold the major read only data in memory for the tests, since it considerably shorten their runtime.

I am also going to see what I can do to mock all the data access code that doesn’t actually test that talking to the database works. It should be simple enough, but I got quite a bit of tests to refactor.