NHibernate, Caching and tests, Oh My!
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.
- The first cache, which I cleared effectively, was the object cache. This holds the values of the cached entities.
- The second cache, which I forgot about, is the collections cache. This holds the association between the entities.
- 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.
Comments
Comment preview