Faking DbContext in Tests with MOQ

You want to test something that uses an Entity Framework DbContext as a dependency.

Can I replace this easily with a fake DbContext?

Using NSubstitute? Read this

Let us say you have this simple DbContext:

public class SomedbContext : DbContext
{
  public virtual DbSet<Blog> Blogs => Set<Blog>();
  public virtual DbSet<Post> Posts => Set<Post>();
}

Do note I am using MOQ to build fake objects, and that I have made these properties virtual to allow MOQ to override their implementation.

Now I need some fake data, and turn it into IQueryable<T>:

IQueryable<Blog> blogs = FakeData.FakeBlogs.AsQueryable();
IQueryable<Post> posts = FakeData.FakePosts.AsQueryable();

This allows me to build a fake DbContext as follows:

SomedbContext db =
  new SomedbContext()
    .WithTable(db => db.Blogs, blogs)
    .WithTable(db => db.Posts, posts)
    .Build();

Now I can pass this fake DbContext to the intended class as a dependency, and it will use my fake data during the test, and no real database is needed.

You might also consider simply using the InMemory database provided by EF.

So how does this work?

Let us examine following piece of code:

SomedbContext db =
  new SomedbContext()
    .WithTable(db => db.Blogs, blogs)
    .WithTable(db => db.Posts, posts)
    .Build();

The first WithTable method is an extension method which returns a fluid builder instance:

public static class DbContextExtensions
{
  public static FakeDbContextBuilder<TDB> WithTable<TDB, E>(
    this TDB dbContext, 
    Expression<Func<TDB, DbSet<E>>> exp,
    IQueryable<E> data)
    where TDB : DbContext
    where E : class
  {
    FakeDbContextBuilder<TDB> builder = new();
    return builder.WithTable(exp, data);
  }

Using the power of generics, I can apply this method to any DbContext, passing the DbSet<E> I want to replace, and the data that it should be replaced with.

new SomedbContext().WithTable(db => db.Blogs, blogs)

There are three arguments involved:

  1. The WithTable method is an extension method, so a SomedbContext instance is passed as the first argument.
  2. The second argument in an Expression<Func<SomedbContext, DbSet<Blog>>>, and db => db.Blogs will be passed allows us to replace Blogs with the fake data.
  3. The third argument is the fake data passed as an IQueryable<Blog>.

All of this gets passed to the FakeDbContextBuilder<TDB> fluent builder's WithTable extension method. Let us have a look at this:

public static FakeDbContextBuilder<TDB> WithTable<TDB, E>(
  this FakeDbContextBuilder<TDB> builder,
  Expression<Func<TDB, DbSet<E>>> exp,
  IQueryable<E> data)
  where TDB : DbContext
  where E : class
{
  Mock<DbSet<E>> mockSet = new();
  mockSet.As<IQueryable<E>>()
    .Setup(m => m.Provider)
    .Returns(data.Provider);
  mockSet.As<IQueryable<E>>()
    .Setup(m => m.Expression)
    .Returns(data.Expression);
  mockSet.As<IQueryable<E>>()
    .Setup(m => m.ElementType)
    .Returns(data.ElementType);
  mockSet.As<IQueryable<E>>()
    .Setup(m => m.GetEnumerator())
    .Returns(data.GetEnumerator);

  builder.Replace(exp, mockSet.Object);
  return builder;
}

This method has the same arguments as the first WithTable method.

What this method does it creates a fake DbSet<E> using MOQ, and then proceeds to relay all of its properties to the IQueryable<T> fake data.

Once this is done, we tell the fluent builder to replace a property in the DbContext, for example replace db => db.Blogs with the fake DbSet<E>.

Now it is time to examine the FakeDbContextBuilder<TDB> class:

public class FakeDbContextBuilder<TDB>
where TDB : DbContext
{
  private Mock<TDB> moq = new();

  public TDB Build()
  => moq.Object;

  public void Replace<E>(Expression<Func<TDB, DbSet<E>>> exp, DbSet<E> e)
    where E : class
  {
    moq.Setup(exp).Returns(e);
  }
}

This class keeps track of a Mock<SomedbContext>, and replaces each DbSet<E> property if needed. This is done in the Replace method.

At the end of the call chain, you should call Build, which returns the fake DbContext.

You don't even have to replace each DbSet, pick you ones you need.

In this blog post I wanted to demonstrate how easy it is to replace any DbContext with fake data, and show you an implementation that make the life of the user (you) easy and hides away the details on how to do this...