Faking an Entity Framework DataContext

One of the toughest things for me with unit testing is managing data. Using transactions isn’t a good idea for unit testing b/c it really slows your tests down. We want them to run as quickly as possible (<5 seconds a test at the most). Creating an in memory database can really simplify your test writing.

I’m going to be expanding this as I get time. My goal is to have an easy swappable in-memory db that acts as close to a real db as possible (i.e. failing in the right places). Current plans are to introduce it via NuGet to the larger community in an easier to use way. Eventually, I’d like to see it used directly in Entity Framework so it is available for everyone out of the box.

I’ve included some of the links and tools which made my current work possible for someone to recreate. It’s pretty rough right now in terms of organization and repeatability, but I will be improving as I go.

Using the Unit Of Work lifetime for DataContext
http://blog.stevensanderson.com/2007/11/29/linq-to-sql-the-multi-tier-story/

Setting up a fake db context
http://refactorthis.wordpress.com/2011/05/31/mock-faking-dbcontext-in-entity-framework-4-1-with-a-generic-repository/

UPDATE: I’ve added a new class and use it instead of the generic list in the t4 file- this allows models in a fake context to map back and forth (i.e. adding class2 to a class1 collection maps class2 back to a class1 property as well). Code is below- it’s a little awkward b/c of the generic cast of the Parent object.

  public class MyApplicationCollection : List, ICollection where T : IMyApplicationModel
	{
        public IMyApplicationModel Parent { get; set; }

        public MyApplicationCollection(IMyApplicationModel parent)
        {
            Parent = parent;

        }

        public void Add(T item)
        {
            var context = MyDomainDataContextHelper.CurrentContext;
            var ct = context.GetType();
            if (ct == typeof (FakeMyApplicationContext))
            {
                var parentType = item.GetType().GetProperty(Parent.GetType().Name);

                if (parentType != null)
                {
                    parentType.SetValue(item, Parent, null);

                }
                else
                {
                    var listOfParentsProp = item.GetType().GetProperty(Parent.GetType().Name + "s");
                    var value = listOfParentsProp.GetValue(item, null);
                    GenericOperator.GenericAdder(value, Parent);
                }
            }
            base.Add(item);
        }
    }

public static class GenericOperator
{
    public static void GenericAdder(dynamic list, dynamic item)
    {
        ConvertItem(list, item);
    }
    public static void ConvertItem(List list, T item)
    {
       list.Add(item);
    }
}
 public Class1()
        {
            this.Class2s= new DreamCollection(this);
        }

Use t4 code generation to automatically create the fake db context (currently using EF Power tools- reverse engineer db)

The link below is for EF Power tools:
EF Power Tools
Instead of doing a direct reverse engineer, first use “Customize Reverse Engineer Templates” and then modify the TT files. I’ve provided an example from my application.

<#@ template hostspecific="true" language="C#" #>
<#@ include file="EF.Utility.CS.ttinclude" #><#@  output extension=".cs" #><#     var efHost = (EfTextTemplateHost)Host;     var code = new CodeGenerationTools(this); #>
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;
using System.Linq;
using System.Reflection;
using MyApplication.Domain.Data;
using MyApplication.Models;

using <#= code.EscapeNamespace(efHost.MappingNamespace) #>;

namespace <#= code.EscapeNamespace(efHost.Namespace) #>
{
    public partial class <#= efHost.EntityContainer.Name #> : DbContext, ICodeFirstEntities
    {
        static <#= efHost.EntityContainer.Name #>()
        {
            Database.SetInitializer<<#= efHost.EntityContainer.Name #>>(null);
        }

        public <#= efHost.EntityContainer.Name #>()
            : base(MyApplication.Domain.Connection.ConnectionInfo.MyApplicationContext)
        {
        }
		public dynamic Set(System.Type typeToFindSetFor)
        {
            foreach (PropertyInfo property in typeof(MyApplicationContext).GetProperties())
            {
                if (typeToFindSetFor.Name==property.Name)
                    return property.GetValue(this, null);
               
                if (typeToFindSetFor.Name.Contains(new string(property.Name.Take(20).ToArray()) + "_"))
                {
                    return property.GetValue(this, null);
                }
            }

		    throw new System.Exception("Type " + typeToFindSetFor.Name  + " not found in MyApplicationContext");
        }

      public void AcceptAllChanges()
        {

            ((IObjectContextAdapter)this).ObjectContext.AcceptAllChanges();
        }

        public void SaveChanges(bool commit)
        {
            if (commit == false)
            {
                ((IObjectContextAdapter) this).ObjectContext.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
            }
            else
            {
                base.SaveChanges();
            }
        }
        public void SaveChanges()
        {

            base.SaveChanges();

        }

        public IDbSet Set(T modelToFindSetFor) where T : class
        {
            foreach (PropertyInfo property in typeof(MyApplicationContext).GetProperties())
            {
                if (property.PropertyType == typeof(IDbSet))
                    return property.GetValue(this, null) as IDbSet;
            }
            throw new System.Exception("Type collection not found");
        }

<#     foreach (var set in efHost.EntityContainer.BaseEntitySets.OfType())     { #>
        public IDbSet<<#= set.ElementType.Name #>> <#= set.ElementType.Name #> { get; set; }
<#     } #>

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
<#     foreach (var set in efHost.EntityContainer.BaseEntitySets.OfType())     { #>
	        modelBuilder.Configurations.Add(new <#= set.ElementType.Name #>Map());
<#     } #>
        }
    }
}
namespace Fake<#= code.EscapeNamespace(efHost.Namespace) #>
{
    public partial class Fake<#= efHost.EntityContainer.Name #> : ICodeFirstEntities
    {
        public IDbSet Set() where T : class
        {
            foreach (PropertyInfo property in typeof(FakeMyApplicationContext).GetProperties())
            {
                if (property.PropertyType == typeof (IDbSet))
                    return property.GetValue(this, null) as IDbSet;
            }
            throw new System.Exception("Type collection not found");
        }

       public IDbSet Set(T modelToFindSetFor) where T : class
        {
            foreach (PropertyInfo property in typeof(FakeMyApplicationContext).GetProperties())
            {
                if (property.PropertyType == typeof (IDbSet))
                    return property.GetValue(this, null) as IDbSet;
            }
            throw new System.Exception("Type collection not found");
        }

        public dynamic Set(System.Type typeToFindSetFor)
        {
            foreach (PropertyInfo property in typeof(FakeMyApplicationContext).GetProperties())
            {
                if (property.Name == typeToFindSetFor.Name)
                    return property.GetValue(this, null);
            }
            throw new System.Exception("Type collection not found");
        }

        public void SaveChanges()
        {
            // do nothing (probably set a variable as saved for testing)
        }
		public void AcceptAllChanges()
		{
		}
		 public void SaveChanges(bool commit)
        {
            // do nothing (probably set a variable as saved for testing)
        }
		public void Dispose()
        {
           //do nothing
        }
		public FakeMyApplicationContext()
        {
            <#     foreach (var set in efHost.EntityContainer.BaseEntitySets.OfType())     { #>
        <#= set.ElementType.Name #>=new FakeDbSet<<#= set.ElementType.Name #>>();
<#     } #>

        }

<#     foreach (var set in efHost.EntityContainer.BaseEntitySets.OfType())     { #>
        public IDbSet<<#= set.ElementType.Name #>> <#= set.ElementType.Name #> { get; set; }
<#     } #>

    }
}
namespace MyApplication.Models
{
    public interface ICodeFirstEntities : System.IDisposable
    {
		IDbSet Set(T modelToFindSetFor) where T : class;
        dynamic Set(System.Type typeToFindSetFor);
        void SaveChanges();
		void SaveChanges(bool commit);
		void AcceptAllChanges();

	<#     foreach (var set in efHost.EntityContainer.BaseEntitySets.OfType())     { #>
        IDbSet<<#= set.ElementType.Name #>> <#= set.ElementType.Name #> { get; set; }
<#     } #>

    }
}

My code generally looks like this(rough example):

        void RunAtStartOfUnitOfWork() //I.e. at the beginning of an HTTP request
        {
            IUnitOfWorkHelper.CurrentDataStore = new HttpContextDataStore();
        }
        void FunctionUsingDataContext()
        {
            IDbContext context = MyDomainDataContextHelper.CurrentContext;   //pulling the Unit of Work singleton dbContext
            SomeDbTable something = context.SomeTable;
            something.column = "Some New Value";
            context.SaveChanges();
        }

You can set up an automatically generated fake database context for use in unit testing(nUnit examples shown below).

    [TestFixture]
    class FakeEntityFrameworkTests
    {
        [Test]
        public void AddRecord_1Record_RecordIsFound()
        {
            UnitOfWorkHelper.CurrentDataStore = new FakeContextDataStore();
            var context = MyDomainDataContextHelper.CurrentContext;
            var app = new Application { ApplicationID = 1 };

            context.Application.Add(app);
            var apps = context.Application.ToList();
            var result = (from a in context.Application
                          where a.ApplicationID == 1
                          select a).Single();

            Assert.AreEqual(1, result.ApplicationID);
        }

        [Test]
        public void DeleteRecord_OneRecordInDb_Returns0Length()
        {
            UnitOfWorkHelper.CurrentDataStore = new FakeContextDataStore();
            var context = MyDomainDataContextHelper.CurrentContext;
            var app = new Application { ApplicationID = 1 };

            context.Application.Add(app);
            context.Application.Remove(app);
            var apps = context.Application.ToList();

            Assert.AreEqual(0, apps.Count);
        }
    }

Leave a comment