In my current project, we were first using a lot of mocking to be able to unit test. This resulted in a very time-consuming way of writing unit tests. After reading a blog post of Richard Garside, this got a whole lot easier.
The idea is simple, we provide a new implementation for DbSet that uses an in-memory store. Using this approach, we can use all the usual methods in a normal way without mocking all of them. We already had an interface for our DbContext, because we use Autofac for dependency injection.
Our DbContext interface
public interface IMyEntities { IDbSet MyEntities { get; set; } void Commit(); void Dispose(); void SetModified(object entity); System.Data.Entity.IDbSet<TEntity> Set() where TEntity : class; }
Our actual DbContext implementation
The actual implementation of the DbContext implements the interface, so we can easily provide another implementation for our unit tests:
public class MyEntities : DbContext, IMyEntities { public IDbSet MyEntities { get; set; } public virtual void Commit() { base.SaveChanges(); } public IDbSet<T> Set<T>() where T : class { return base.Set<T>(); } public void SetModified(object entity) { Entry(entity).State = EntityState.Modified; } }
In-memory DbContext implementation
The implementation of our in-memory DbSet is displayed below. In the constructor, we set each entity set we use to a specific Fake implementation. We will get to that in a bit.
public class FakeMyEntities : DbContext, IMyEntities { public FakeMyEntities() { MyEntities = new FakePersonSet(); } public IDbSet MyEntities { get; set; } public virtual void Commit() { } public IDbSet<T> Set<T>() where T : class { foreach (PropertyInfo property in typeof(FakeMyEntities).GetProperties()) { if (property.PropertyType == typeof(IDbSet<T>)) return property.GetValue(this, null) as IDbSet<T>; } throw new Exception("Type collection not found"); } public void SetModified(object entity) { } }
In-memory DbSet base and specific sets
The base class for all FakeDbSets looks like this:
public class FakeDbSet<T> : IDbSet<T> where T : class { HashSet<T> _data; IQueryable _query; public FakeDbSet() { _data = new HashSet<T>(); _query = _data.AsQueryable(); } public virtual T Find(params object[] keyValues) { throw new NotImplementedException("Create a Fake(object)Set in Pegasus.Tests.Infrastructure.FakeSet"); } public T Add(T item) { _data.Add(item); return item; } public T Remove(T item) { _data.Remove(item); return item; } public T Attach(T item) { _data.Add(item); return item; } public T Detach(T item) { _data.Remove(item); return item; } Type IQueryable.ElementType { get { return _query.ElementType; } } System.Linq.Expressions.Expression IQueryable.Expression { get { return _query.Expression; } } IQueryProvider IQueryable.Provider { get { return _query.Provider; } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return _data.GetEnumerator(); } IEnumerator<T> IEnumerable<T>.GetEnumerator() { return _data.GetEnumerator(); } public TDerivedEntity Create() where TDerivedEntity : class, T { throw new NotImplementedException("Derive from FakeDbSet and override Create"); } public T Create() { throw new NotImplementedException("Derive from FakeDbSet and override Create"); } public System.Collections.ObjectModel.ObservableCollection<T> Local { get { throw new NotImplementedException("Derive from FakeDbSet and override Local"); } } }
All we have to do now for each entity set, is to create a FakeDbSet and implement the Find method, as the primary key can be different for each Model object.
public class FakePersonSet : FakeDbSet { public override Order Find(params object[] keyValues) { return this.SingleOrDefault(e => e.SocialSecurityNumber == Convert.ToInt32(keyValues.Single())); } }
Now you just have to make sure that your unit tests use the FakeDbContext instead of the real one, and your code will just work. It requires a bit of setup, but once this works, writing your unit tests is a lot easier.