I've been doing this with a mock DbContext and mock DbSet that I've created. They store test data in memory and allow you to do most of the standard things you can do on a DbSet.
Your code that acquires the DbContext initially will have to be changed so that it acquires a MockDbContext when it is running under unit test. You can determine if you are running under MSTest with the following code:
public static bool IsInUnitTest
{
get
{
return AppDomain.CurrentDomain.GetAssemblies()
.Any(assembly =>
assembly.FullName.StartsWith(
"Microsoft.VisualStudio.QualityTools.UnitTestFramework"));
}
}
Here is the code for MockDbContext:
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication5
{
// ProductionDbContext would be DbContext class
// generated by Entity Framework
public class MockDbContext: ProductionDbContext
{
public MockDbContext()
{
LoadFakeData();
}
// Entities (for which we'll provide MockDbSet implementation
// and test data)
public override DbSet<Account> Accounts { get; set; }
public override DbSet<AccountGenLink> AccountGenLinks { get; set; }
public override DbSet<AccountPermit> AccountPermits { get; set; }
public override DbSet<AcctDocGenLink> AcctDocGenLinks { get; set; }
// DbContext method overrides
private int InternalSaveChanges()
{
// Just return 0 in the mock
return 0;
}
public override int SaveChanges()
{
return InternalSaveChanges();
}
public override Task<int> SaveChangesAsync()
{
return Task.FromResult(InternalSaveChanges());
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
// Just ignore the cancellation token in the mock
return SaveChangesAsync();
}
private void LoadFakeData()
{
// Tables
Accounts = new MockDbSet<Account>(this);
Accounts.AddRange(new List<Account>
{
new Account
{
SSN_EIN = "123456789", CODE = "A", accttype = "CD",
acctnumber = "1", pending = false, BankOfficer1 = string.Empty,
BankOfficer2 = null, Branch = 0, type = "18", drm_rate_code = "18",
officer_code = string.Empty, open_date = new DateTime(2010, 6, 8),
maturity_date = new DateTime(2010, 11, 8), HostAcctActive = true,
EffectiveAcctStatus = "A"
},
new Account
{
SSN_EIN = "123456789", CODE = "A", accttype = "DD",
acctnumber = "00001234", pending = false, BankOfficer1 = "BCK",
BankOfficer2 = string.Empty, Branch = 0, type = "05", drm_rate_code = "00",
officer_code = "DJT", open_date = new DateTime(1998, 9, 14),
maturity_date = null, HostAcctActive = true,
EffectiveAcctStatus = "A"
},
new Account
{
SSN_EIN = "123456789", CODE = "A", accttype = "LN", acctnumber = "1",
pending = false, BankOfficer1 = "LMP", BankOfficer2 = string.Empty,
Branch = 0, type = "7", drm_rate_code = null, officer_code = string.Empty,
open_date = new DateTime(2001, 10, 24),
maturity_date = new DateTime(2008, 5, 2), HostAcctActive = true,
EffectiveAcctStatus = "A"
}
});
AccountGenLinks = new MockDbSet<AccountGenLink>(this);
AccountGenLinks.AddRange(new List<AccountGenLink>
{
// Add your test data here if needed
});
AccountPermits = new MockDbSet<AccountPermit>(this);
AccountPermits.AddRange(new List<AccountPermit>
{
// Add your test data here if needed
});
AcctDocLinks = new MockDbSet<AcctDocLink>(this);
AcctDocLinks.AddRange(new List<AcctDocLink>
{
new AcctDocLink { ID = 1, SSN_EIN = "123456789", CODE = "A", accttype = "DD",
acctnumber = "00001234", DocID = 50, DocType = 5 },
new AcctDocLink { ID = 25, SSN_EIN = "123456789", CODE = "6", accttype = "CD",
acctnumber = "1", DocID = 6750, DocType = 5 }
});
}
}
}
And here is the code for MockDbSet:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication5
{
public sealed class MockDbSet<TEntity> : DbSet<TEntity>, IQueryable,
IEnumerable<TEntity>, IDbAsyncEnumerable<TEntity> where TEntity : class
{
public MockDbSet(MockDbContext context)
{
// Get entity set for entity
// Used when we figure out whether to generate
// IDENTITY values
EntitySet = ((IObjectContextAdapter) context).ObjectContext
.MetadataWorkspace
.GetItems<EntityContainer>(DataSpace.SSpace).First()
.BaseEntitySets
.FirstOrDefault(item => item.Name == typeof(TEntity).Name);
Data = new ObservableCollection<TEntity>();
Query = Data.AsQueryable();
}
private ObservableCollection<TEntity> Data { get; set; }
Type IQueryable.ElementType
{
get { return Query.ElementType; }
}
private EntitySetBase EntitySet { get; set; }
Expression IQueryable.Expression
{
get { return Query.Expression; }
}
IEnumerator IEnumerable.GetEnumerator()
{
return Data.GetEnumerator();
}
public override ObservableCollection<TEntity> Local
{
get { return Data; }
}
IQueryProvider IQueryable.Provider
{
get { return new MockDbAsyncQueryProvider<TEntity>(Query.Provider); }
}
private IQueryable Query { get; set; }
public override TEntity Add(TEntity entity)
{
GenerateIdentityColumnValues(entity);
Data.Add(entity);
return entity;
}
public override IEnumerable<TEntity> AddRange(IEnumerable<TEntity> entities)
{
foreach (var entity in entities)
Add(entity);
return entities;
}
public override TEntity Attach(TEntity entity)
{
return Add(entity);
}
public override TEntity Create()
{
return Activator.CreateInstance<TEntity>();
}
public override TDerivedEntity Create<TDerivedEntity>()
{
return Activator.CreateInstance<TDerivedEntity>();
}
public override TEntity Find(params object[] keyValues)
{
throw new NotSupportedException();
}
public override Task<TEntity> FindAsync(params object[] keyValues)
{
return FindAsync(CancellationToken.None, keyValues);
}
public override Task<TEntity> FindAsync(CancellationToken cancellationToken, params object[] keyValues)
{
throw new NotSupportedException();
}
private void GenerateIdentityColumnValues(TEntity entity)
{
// The purpose of this method, which is called when adding a row,
// is to ensure that Identity column values are properly initialized
// before performing the add. If we were making a "real" Entity Framework
// Add() call, this task would be handled by the data provider and the
// value(s) would then be propagated back into the entity. In the case
// of this mock, there is nothing that will do that, so we have to make
// this at-least token effort to ensure the columns are properly initialized.
// In SQL Server, an Identity column can be of one of the following
// data types: tinyint, smallint, int, bigint, decimal (with a scale of 0),
// or numeric (with a scale of 0); This method handles the integer types
// (the others are typically not used).
foreach (var member in EntitySet.ElementType.Members.ToList())
{
if (member.IsStoreGeneratedIdentity)
{
// OK, we've got a live one; do our thing.
//
// Note that we'll get the current value of the column and,
// if it is nonzero, we'll leave it alone. We do this because
// the test data in our mock DbContext provides values for the
// Identity columns and many of those values are foreign keys
// in other entities (where we also provide test data). We don't
// want to disturb any existing relationships defined in the test data.
Type columnDataType = null;
foreach (var metadataProperty in member.TypeUsage.EdmType.MetadataProperties.ToList())
{
if (metadataProperty.Name != "PrimitiveTypeKind")
continue;
switch ((PrimitiveTypeKind)metadataProperty.Value)
{
case PrimitiveTypeKind.SByte:
columnDataType = typeof(SByte);
break;
case PrimitiveTypeKind.Int16:
columnDataType = typeof(Int16);
break;
case PrimitiveTypeKind.Int32:
columnDataType = typeof(Int32);
break;
case PrimitiveTypeKind.Int64:
columnDataType = typeof(Int64);
break;
default:
throw new InvalidOperationException();
}
var identityColumnGetter = entity.GetType().GetProperty(member.Name).GetGetMethod();
var identityColumnSetter = entity.GetType().GetProperty(member.Name).GetSetMethod();
Int64 specifiedColumnValue = 0;
switch (columnDataType.Name)
{
case "SByte":
specifiedColumnValue = (SByte)identityColumnGetter.Invoke(entity, null);
break;
case "Int16":
specifiedColumnValue = (Int16)identityColumnGetter.Invoke(entity, null);
break;
case "Int32":
specifiedColumnValue = (Int32)identityColumnGetter.Invoke(entity, null);
break;
case "Int64":
specifiedColumnValue = (Int64)identityColumnGetter.Invoke(entity, null);
break;
}
if (specifiedColumnValue != 0)
break;
Int64 maxExistingColumnValue = 0;
switch (columnDataType.Name)
{
case "SByte":
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (SByte)identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] { (SByte)(++maxExistingColumnValue) });
break;
case "Int16":
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (Int16)identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] { (Int16)(++maxExistingColumnValue) });
break;
case "Int32":
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (Int32)identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] { (Int32)(++maxExistingColumnValue) });
break;
case "Int64":
foreach (var item in Local.ToList())
maxExistingColumnValue = Math.Max(maxExistingColumnValue, (Int64)identityColumnGetter.Invoke(item, null));
identityColumnSetter.Invoke(entity, new object[] { (Int64)(++maxExistingColumnValue) });
break;
}
}
}
}
}
IDbAsyncEnumerator<TEntity> IDbAsyncEnumerable<TEntity>.GetAsyncEnumerator()
{
return new MockDbAsyncEnumerator<TEntity>(Data.GetEnumerator());
}
IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
{
return Data.GetEnumerator();
}
public override TEntity Remove(TEntity entity)
{
Data.Remove(entity);
return entity;
}
public override IEnumerable<TEntity> RemoveRange(IEnumerable<TEntity> entities)
{
foreach (var entity in entities)
Remove(entity);
return entities;
}
public override DbSqlQuery<TEntity> SqlQuery(string sql, params object[] parameters)
{
throw new NotSupportedException();
}
}
internal class MockDbAsyncQueryProvider<TEntity> : IDbAsyncQueryProvider
{
internal MockDbAsyncQueryProvider(IQueryProvider queryProvider)
{
QueryProvider = queryProvider;
}
private IQueryProvider QueryProvider { get; set; }
public IQueryable CreateQuery(Expression expression)
{
return new MockDbAsyncEnumerable<TEntity>(expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new MockDbAsyncEnumerable<TElement>(expression);
}
public object Execute(Expression expression)
{
return QueryProvider.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return QueryProvider.Execute<TResult>(expression);
}
public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute(expression));
}
public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute<TResult>(expression));
}
}
internal class MockDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T>
{
public MockDbAsyncEnumerable(IEnumerable<T> enumerable)
: base(enumerable)
{
}
public MockDbAsyncEnumerable(Expression expression)
: base(expression)
{
}
IQueryProvider IQueryable.Provider
{
get { return new MockDbAsyncQueryProvider<T>(this); }
}
public IDbAsyncEnumerator<T> GetAsyncEnumerator()
{
return new MockDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return GetAsyncEnumerator();
}
}
internal class MockDbAsyncEnumerator<T> : IDbAsyncEnumerator<T>
{
public MockDbAsyncEnumerator(IEnumerator<T> enumerator)
{
Enumerator = enumerator;
}
public void Dispose()
{
Enumerator.Dispose();
}
public T Current
{
get { return Enumerator.Current; }
}
object IDbAsyncEnumerator.Current
{
get { return Current; }
}
private IEnumerator<T> Enumerator { get; set; }
public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
{
return Task.FromResult(Enumerator.MoveNext());
}
}
}