Testing With a Fake DbContext

Posted on February 14, 2012. Filed under: Entity Framework, Testing, Visual Studio | Tags: , , , , , , , |

One of the most popular posts on my blog has been “EF CTP4 Tips & Tricks: Testing With Fake DbContext”. That post was built on a pre-release version of Entity Framework so I thought I’d provide an updated post on top of the DbContext API surface we ended up shipping.

In this post we are going to take a look at how to replace a DbContext based data access layer with an in-memory fake for unit testing.

I’m going to use an MVC controller as an example but the same approach can be used for any component that consumes a derived DbContext, including repositories.

 

The Problem

Say we have a very simple model for Employees and Departments and we are using DbContext to persist and query our data:

public class EmployeeContext : DbContext
{
    public DbSet<Department> Departments { get; set; }
    public DbSet<Employee> Employees { get; set; }
}

public class Department
{
    public int DepartmentId { get; set; }
    public string Name { get; set; }

    public ICollection<Employee> Employees { get; set; }
}

public class Employee
{
    public int EmployeeId { get; set; }
    public int FirstName { get; set; }
    public int LastName { get; set; }
    public int Position { get; set; }

    public Department Department { get; set; }
}

 

In an MVC application we could add a controller that can display all departments in alphabetical order:
(Typically we’d also implement ‘detail’, ‘create’, ‘edit’ and ‘delete’ functionality but I’m leaving them out because ‘list’ is enough to demonstrate fakes)

public class DepartmentController : Controller
{
    private EmployeeContext db = new EmployeeContext();

    public ViewResult Index()
    {
        return View(db.Departments.OrderBy(d => d.Name).ToList());
    }

    protected override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }
}

 

The issue is that our controller now has a hard dependency on EF because it needs an EmployeeContext which derives from DbContext and exposes DbSets. We have no way to replace this with any other implementation and we are forced to run any unit tests against a real database, which means they really aren’t unit tests at all.

 

Adding an Interface

DbSet<T> happens to implement IDbSet<T> so we can pretty easily create an interface that our derived context implements:

public interface IEmployeeContext
{
    IDbSet<Department> Departments { get; }
    IDbSet<Employee> Employees { get; }
    int SaveChanges();
}

public class EmployeeContext : DbContext, IEmployeeContext
{
    public IDbSet<Department> Departments { get; set; }
    public IDbSet<Employee> Employees { get; set; }
}

 

Now we can update our controller to be based on this interface rather than the EF specific implementation.
(You’ll notice I’m still using the EF implementation in the default constructor, in a real world app you would probably just have the interface based constructor and use dependency injection to supply the EF implementation at runtime, but I just wanted to keep things simple for this post.)

public class DepartmentController : Controller
{
    private IEmployeeContext db;

    public DepartmentController()
    {
        this.db = new EmployeeContext();
    }

    public DepartmentController(IEmployeeContext context)
    {
        this.db = context;
    }

    public ViewResult Index()
    {
        return View(db.Departments.OrderBy(d => d.Name).ToList());
    }

    protected override void Dispose(bool disposing)
    {
        if (db is IDisposable)
        {
            ((IDisposable)db).Dispose();
        }
        base.Dispose(disposing);
    }
}

 

Building Fakes

The first thing we need is a fake implementation of IDbSet<TEntity>, this is pretty easy to implement.

public class FakeDbSet<T> : IDbSet<T>
    where T : class
{
    ObservableCollection<T> _data;
    IQueryable _query;

    public FakeDbSet()
    {
        _data = new ObservableCollection<T>();
        _query = _data.AsQueryable();
    }

    public virtual T Find(params object[] keyValues)
    {
        throw new NotImplementedException("Derive from FakeDbSet<T> and override Find");
    }

    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;
    }

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

    public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
    {
        return Activator.CreateInstance<TDerivedEntity>();
    }

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

    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();
    }
}

 

 

There isn’t really a good way to generically implement Find, so I’ve left it as a virtual method that throws if called. If our application makes use of the Find method we can create an implementation specific to each type.

public class FakeDepartmentSet : FakeDbSet<Department>
{
    public override Department Find(params object[] keyValues)
    {
        return this.SingleOrDefault(d => d.DepartmentId == (int)keyValues.Single());
    }
}

public class FakeEmployeeSet : FakeDbSet<Employee>
{
    public override Employee Find(params object[] keyValues)
    {
        return this.SingleOrDefault(e => e.EmployeeId == (int)keyValues.Single());
    }
}

 

 

Now we can create a fake implementation of our context:

public class FakeEmployeeContext : IEmployeeContext
{
    public FakeEmployeeContext()
    {
        this.Departments = new FakeDepartmentSet();
        this.Employees = new FakeEmployeeSet();
    }

    public IDbSet<Department> Departments { get; private set; }

    public IDbSet<Employee> Employees { get; private set; }

    public int SaveChanges()
    {
        return 0;
    }
}

Testing Against Fakes

Now that we have our fakes defined we can use them to write a unit test for our controller, that doesn’t use EF:

[TestMethod]
public void IndexOrdersByName()
{
    var context = new FakeEmployeeContext
    {
        Departments =
        {
            new Department { Name = "BBB"},
            new Department { Name = "AAA"},
            new Department { Name = "ZZZ"},
        }
    };

    var controller = new DepartmentController(context);
    var result = controller.Index();

    Assert.IsInstanceOfType(result.ViewData.Model, typeof(IEnumerable<Department>));
    var departments = (IEnumerable<Department>)result.ViewData.Model;
    Assert.AreEqual("AAA", departments.ElementAt(0).Name);
    Assert.AreEqual("BBB", departments.ElementAt(1).Name);
    Assert.AreEqual("ZZZ", departments.ElementAt(2).Name);
}

 

 

Summary

In this post we saw how to build an interface that represents our context and how to build an in-memory fake of that context for use in our unit tests. We used an MVC controller to demonstrate this but the same approach can be used with any component that needs to interact with an EF based context, including repositories. There are a number of reasons to use in-memory fakes for unit testing but some key benefits are stable and robust tests that execute quickly and exercise a single component, making failures easy to isolate.

    About

    Rowan works as a Program Manager for the ADO.NET Entity Framework team at Microsoft. He speaks at technical conferences and blogs at romiller.com. Rowan lives in Seattle, Washington with his wife Athalie. Prior to moving to the US he resided in the small state of Tasmania in Australia. Outside of technology Rowan's passions include snowboarding, mountain biking, horse riding, rock climbing and pretty much anything else that involves being active. The primary focus of his life, however, is to follow Jesus.

    RSS

    Subscribe Via RSS

    • Subscribe with Bloglines
    • Add your feed to Newsburst from CNET News.com
    • Subscribe in Google Reader
    • Add to My Yahoo!
    • Subscribe in NewsGator Online
    • The latest comments to all posts in RSS

    Meta

Liked it here?
Why not try sites on the blogroll...

%d bloggers like this: