Testable Data Access With The Repository Pattern
In my last post I explored a little about how we use the Entity Framework. One question that comes up a lot is how do you test your services with the data access layer without hitting the database. Not hitting the database during tests is not only a performance issue. Unless you build and tear down your data on every run you have to make sure the test data is consistent. So how do we do it?
The first step is to create an in-memory version of the repository. This implementation will keep data in memory instead of hitting the database. It is important to build this in-memory version early and evolve it with the real repository. By having an in-memory implementation of the repository you immediately get a warning flag if you try to exploit some esoteric feature of your ORM or other data access technology. Not that it is inherently bad to exploit such features, but data access technologies come and go so this should be done carefully. (L2S anyone?)
Note that the implementation below is not only for unit tests. You can inject this implementation instead of a real one in the application to run it with consistent data.
Here’s a refresher of the interface.
public interface IRepository : IDisposable
{
T[] GetAll<T>();
T[] GetAll<T>(Expression<Func<T, bool>> filter);
T GetSingle<T>(Expression<Func<T, bool>> filter);
T GetSingle<T>(Expression<Func<T, bool>> filter, List<Expression<Func<T, object>>> subSelectors);
void Delete<T>(T entity);
void Add<T>(T entity);
int SaveChanges();
DbTransaction BeginTransaction();
}
Now let’s jump to the incredibly complex implementation of the in-memory implementation of the repository. :-)
As you can see below, the code to implement a in-memory repository is remarkably slim. Despite the fact that the code is not rocket science, let’s walk through some interesting parts. The filters are all implemented as Expressions<>. In the in-memory implementation we don’t need to do anything with the expressions, so we compile them and pass them as specifications to Linq to Objects.
Another interesting point (IMHO) is the fact that it has become so simple to work with types. You just just call .OfType<> and pass in the type you want and Linq does the rest.
The last point I would like to emphasize, is the use of a NullTransaction. We don’t need transactions for our in-memory implementation, but the service code might call any of the methods on the DbTransaction object returned from BeginTransaction. By the use of a NullTransaction (See code below) we simulate transactions. (I guess you could use a mocking framework for some of this, but I’m not there yet…)
Let’s dissect how this works:
public T GetSingle<T>(Expression<System.Func<T, bool>> filter)
{
var predicate = filter.Compile();
return _storage.OfType<T>().Where(p => predicate(p)).FirstOrDefault();
}
The first step is to compile the expression so that we end up with a predicate that we use in our Linq query. In the next line we first specify the type T, which was passed in by the caller so we just pass that on. Next we call OfType<T> to filter on only the requested type and then we filter using Where() passing in our predicate. We then return to the caller either the first instance found or nothing. That’s it.
Here’s the full implementation. (Note: The SaveChanges() just returns 1. If you have service logic that depends on the actual return value you will have to track changes yourself.)
public class InMemoryRepository : IRepository
{
private List<object> _storage = new List<object>();
#region IRepository Members
public T[] GetAll<T>()
{
return _storage.OfType<T>().ToArray();
}
public T[] GetAll<T>(Expression<System.Func<T, bool>> filter)
{
var predicate = filter.Compile();
return _storage.OfType<T>().Where(p => predicate(p)).ToArray();
}
public T GetSingle<T>(Expression<System.Func<T, bool>> filter)
{
var predicate = filter.Compile();
return _storage.OfType<T>().Where(p => predicate(p)).FirstOrDefault();
}
public T GetSingle<T>(Expression<System.Func<T, bool>> filter, List<Expression<System.Func<T, object>>> subSelectors)
{
// no need for sub selectors in L2O.
return GetSingle<T>(filter);
}
public void Delete<T>(T entity)
{
_storage.Remove(entity);
}
public void Add<T>(T entity)
{
_storage.Add(entity);
}
public int SaveChanges()
{
return 1;
}
public DbTransaction BeginTransaction()
{
return new NullTransaction();
}
#endregion
#region IDisposable Members
public void Dispose()
{
// nothing to dispose here
}
#endregion
}
Here is the implementation of the NullTransaction
public class NullTransaction : DbTransaction
{
protected override DbConnection DbConnection
{
get { throw new NotImplementedException(); }
}
public override IsolationLevel IsolationLevel
{
get { throw new NotImplementedException(); }
}
public override void Commit()
{
// do nothing
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
public override void Rollback()
{
// do nothing
}
}
Now you can easily pre-populate the in-memory repository with consistent data so that you can test your application code easier.