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.