Faking DbContext in Entity Framework 4.1 with a generic repository

Update 30/11/2011: FakeDbSet implementation update Please see the new and improved FakeDbSet Here

Update 16/06/2011:  Added step (2) description of how to implement Set<>() method in your original DbContext so that it returns IDbSet<>. Also added SaveChanges() to expose the context as a unit of work. + A little reorganisation.

Faking of the new Entity Framework 4.1 DbContext can be done quite simply by following these steps:

  1. Create a common interface for your particular DbContext type.

I'm using a generic repository so my interface only needs to implement the Set method. But you could of course expose all your collections through this interface.

    public interface IMainModuleContext
    {
        IDbSet<Person> People { get; set; } // My collections...
        IDbSet<TEntity> Set<TEntity>() where TEntity : class;
        void SaveChanges();
    }

Notice how our DbSet collections IDbSet instead of DbSet. This is because we will use an in-memory representation of the DbSet collection called FakeDbSet which implements IDbSet.

If you are exposing all of your collections and using model-first this could be generated with a T4 Template to save development time. Ensure your real DbContext implements this interface, and that your repository will take a IMainModuleContext instead of the concrete type.

2. Now lets make sure our original context (mine is called MainModuleContext) is implementing this interface. Example of the code to do this is below:

public partial class MainModuleContext : DbContext, IMainModuleContext  
{
    public IDbSet<Person> People { get; set; }
    public MainModuleContext() : base() {}
    public IDbSet<TEntity> Set<TEntity>() where T : class
    {
        return base.Set<TEntity>();
    }

    public void SaveChanges()
    {
        base.SaveChanges();
    }
    // Other methods
}

Notice our properties must return IDbSet instead of DbSet. This is easy since the EF team have included the IDbSet interface for us.

  1. Now we will create a fake dbset (an in-memory representation of a dbset)
    public class FakeDbSet<T> : IDbSet<T> where T : class
    {
        private HashSet<T> _data;

        public FakeDbSet()
        {
            _data = new HashSet<T>();
        }

        public virtual T Find(params object[] keyValues)
        {
            throw new NotImplementedException();
        }

        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 void Detach(T item)
        {
             _data.Remove(item);
        }

        Type IQueryable.ElementType
        {
            get { return _data.AsQueryable().ElementType; }
        }

        Expression IQueryable.Expression
        {
            get { return _data.AsQueryable().Expression; }
        }

        IQueryProvider IQueryable.Provider
        {
            get { return _data.AsQueryable().Provider; }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _data.GetEnumerator();
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return _data.GetEnumerator();
        }

        public T Create()
        {
            return Activator.CreateInstance<T>();
        }

        public ObservableCollection<T> Local
        {
            get
            {
            return new ObservableCollection<T>(_data);
            }
        }

        public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
        {
            return Activator.CreateInstance<TDerivedEntity >();
        }
    }
  1. Now implement your fake context. The only tricky thing here is the Set method needs to use reflection to find the property we are after.
    public partial class FakeMainModuleContext : IMainModuleContext
    {
        public IDbSet<Person> People { get; set; }
        public IDbSet<T> Set<T>() where T : class
        {
            foreach (PropertyInfo property in typeof(FakeMainModuleContext).GetProperties())
            {
                if (property.PropertyType == typeof(IDbSet<T>))
                    return property.GetValue(this, null) as IDbSet<T>;
            }
            throw new Exception("Type collection not found");
        }

        public void SaveChanges()
        {
             // do nothing (probably set a variable as saved for testing)
        }

        public FakeMainModuleContext()
        {
            // Set up your collections
            People = new FakeDbSet
            {
                new Person() { FirstName = "Brent" }
            };
        }

You can now swap out your DbContext with a FakeDbContext for unit testing.

comments powered by Disqus