Mocking EntityFramework context – Code First
When I first try to write test for class that use directly, I found that I can’t mock database context directly using for example Moq
library. That’s because this class properties isn’t virtual. After some research I found that EF6 could be set up to enable mocking [1,2]. However that is not quite what I need. I had some specific requirements:
- I want to trace operations into database tables
- It must work for Code First EF6 configuration
- It should be generic solution for each database table
Let’s try to satisfy first requirement. We need some method to store all data our mocked object. I decided to write in-memory DbSet
object like in [2] link.
public class FakeDbSetKeyed<T, TKey> : DbSet<T>, IQueryable, IEnumerable<T>
where T : class, IKey<TKey>
{
Dictionary<TKey, T> _data;
IQueryable _query;
public FakeDbSetKeyed()
{
_data = new Dictionary<TKey, T>();
_query = _data.Values.AsQueryable();
}
public override T Find(params object[] keyValues)
{
return _data[(TKey)keyValues.First()];
}
public override T Add(T item)
{
_data.Add(item.Id, item);
return item;
}
public override T Remove(T item)
{
_data.Remove(item.Id);
return item;
}
public override T Attach(T item)
{
if (_data.ContainsKey(item.Id))
{
_data[item.Id] = item;
}
else
{
_data.Add(item.Id, item);
}
return item;
}
public T Detach(T item)
{
_data.Remove(item.Id);
return item;
}
public override T Create()
{
return Activator.CreateInstance<T>();
}
public override TDerivedEntity Create<TDerivedEntity>()// where TDerivedEntity : class, T
{
return Activator.CreateInstance<TDerivedEntity>();
}
public override ObservableCollection<T> Local
{
get { return new ObservableCollection<T>(_data.Values); }
}
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.Values.GetEnumerator();
}
public void Clear()
{
_data.Clear();
}
}
You can see two addition to original class definition from MSDN. I added also one additional method (Clear) for purpose of easier operate data sets during testing and also I added generic parameter TKey to specify key type. That enables me to write Find method.
Next, we consider second point of requirement – Code First compatibility. In mentioned MSDN articles we can set up mock for DbContext
, but for Code First approach there isn’t work. That’s because during the creation of Mock
Let’s suppose that we have following DbContext
public class ItemsContext : DbContext
{
public ItemsContext()
: base("DatabaseName")
{
}
public virtual DbSet<User> Users { get; set; }
public virtual DbSet<Item> Items { get; set; }
}
This context isn’t ready to support its different implementation yet. So I defined interface IItemsContext
with sets defined in main context class. I also implement IItemsContext
in ItemsContext
and change application to use interface beside of specific class.
public interface IItemsContext
{
DbSet<User> Users { get; set; }
DbSet<Item> Items { get; set; }
}
public class ItemsContext : DbContext, IItemsContext
{
public ItemsContext()
: base("DatabaseName")
{
}
public virtual DbSet<User> Users { get; set; }
public virtual DbSet<Item> Items { get; set; }
}
Thanks to this modifications in program structure I can write test initializations just like this:
[TestInitialize]
public virtual void TestInitialize()
{
_dataSet = new FakeDbSetKeyed<TEntity, TKey>();
var mockContext = new Mock<IBwEntities>(MockBehavior.Strict
mockContext.Setup(c => c.Set<TEntity>()).Returns(_dataSet);
_dataSet.Clear();
}
With this set up we fill _dataSet and operate on DbContext (ItemsContext for us) in tests in order to check _dataSet
for the results of tested actions.
[1] https://msdn.microsoft.com/en-us/data/dn314429.aspx
[2] https://msdn.microsoft.com/en-us/data/dn314431.aspx