Pragmatic unit testing with Entity Framework 6

 

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.