What are best practices for Unit Testing methods that use cache heavily?

Technology CommunityCategory: Layering & MiddlewareWhat are best practices for Unit Testing methods that use cache heavily?
VietMX Staff asked 5 months ago
Problem

Consider

IList<TObject> AllFromCache() { ... }

TObject FetchById(guid id) { ... }

IList<TObject> FilterByPropertry(int property) { ... }

Fetch.. and Filter.. would call AllFromCache which would populate cache and return if it isn’t there and just return from it if it is. What are best practices for Unit Testing against this type of structure?

  • First of all, move AllFromCache() into a repository class and call it GetAll() to comply with Single Responsibility Principle. That it retrieves from the cache is an implementation detail of the repository and shouldn’t be known by the calling code.
  • Second, wrap the class that gets the data from the database (or wherever) in a caching wrapper. AOP is a good technique for this. It’s one of the few things that it’s very good at.
public class ProductManager
{
    private IProductRepository ProductRepository { get; set; }

    public ProductManager
    {
        ProductRepository = productRepository;
    }

    Product FetchById(guid id) { ... }

    IList<Product> FilterByPropertry(int property) { ... }
}

public interface IProductRepository
{
    IList<Product> GetAll();
}

public class SqlProductRepository : IProductRepository
{
    public IList<Product> GetAll()
    {
        // DB Connection, fetch
    }
}

public class CachedProductRepository : IProductRepository
{
    private IProductRepository ProductRepository { get; set; }

    public CachedProductRepository (IProductRepository productRepository)
    {
        ProductRepository = productRepository;
    }

    public IList<Product> GetAll()
    {
        // Check cache, if exists then return, 
        // if not then call GetAll() on inner repository
    }
}
  • 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.
  • Of course the cache itself also needs unit testing then, for which you have to mock anything it depends on, and so on.