How to Mock Entity Framework’s DbContext for Unit Testing

Unit Testing

One of the frustrating things (there are more) when trying to mock Microsoft’s Entity Framework ORM is that it isn’t unit test friendly. In practice, I tend to wrap the Entity Framework classes in a repository abstraction layer, which gives me control over the interface, so writing unit tests becomes a relatively trivial exercise.

The problem with unit testing code that uses Entity Framework classes, such as DbContext, is that the classes are difficult to mock. While the situation will improve with the introduction of Entity Framework 7 and its in-memory data store, for now we still have to find a way of mocking DbContext if we want to test code that uses it directly.

I’m regularly asked about how to Mock Entity Framework’s DbContext for Unit Testing. For some developers, hiding Entity Framework behind an abstraction layer may not be an option. They may be dealing with legacy code or require 100% code coverage in their unit tests. Or they may just prefer to use the bare metal of Entity Framework directly. Software development is (mostly) a democracy.

Creating a simple library that uses Entity Framework

Let’s create a customer entity with an e-mail and a subscription fee.

public class Customer
{
    public string Email { get; set; }

    public long Id { get; set; }

    public decimal SubscriptionFee { get; set; }
}

We can imagine we have a free tier and a premier (paid) tier. The premier tier customers pay for enhanced service.

Our DbContext-derived class for persisting/retrieving our customers to/from the database is

public class RepositoryContext : DbContext, IRepositoryContext
{
    public IDbSet<Customer> Customers { get; set; }
}

Note that we are exposing our customers as an IDbSet rather than DbSet. Changing from DbSet to IDbSet usually doesn’t cause any problems, but it makes it much easier to mock Entity framework. It’s possible to mock DbContext-derived classes that expose concrete DbSet properties, but it’s more work.

Now we can extract an interface from RepositoryContext.

public interface IRepositoryContext
{
    IDbSet<Customer> Customers { get; set; }
}

Finally, we use this interface in a helper class that calculates overall revenue across our entire customer base.

public static class RevenueCalculator
{
    public static decimal Calculate(IRepositoryContext repositoryContext)
    {
        return repositoryContext.Customers.Sum(x => x.SubscriptionFee);
    }
}

How to Mock Entity Framework’s DbContext using NSubstitute

My mocking library of choice is NSubstitute. I like it because the syntax is clean. It doesn’t require you to pay the “lambda tax” imposed by competing libraries.

We’ll use NSubstitute to mock our DbContext-derived repository here. Other mocking frameworks could be used in a similar fashion.

First let’s create some data that will be returned by our IRepositoryContext fake.

IQueryable<Customer> customers =
    new List<Customer>
    {
        new Customer { SubscriptionFee = 0 },
        new Customer { SubscriptionFee = 5 },
        new Customer { SubscriptionFee = 5 },
        new Customer { SubscriptionFee = 0 },
        new Customer { SubscriptionFee = 5 }
    }.AsQueryable();

We have three customers that pay for our $5 premier tier and two who are on the free tier. Hence our total revenue is $15. Note that we have returned our list as an IQueryable.

OK…here’s the bit we are interested in. How do we mock an IRepositoryContext to inject into RevenueCalculator.Calculate?

We have to mock a number of properties of IDbSet that are used by Entity Framework. Fortunately, these properties are also exposed by our IQueryable object—which is why we converted our customer list to an IQueryable.

IDbSet<Customer> customerDbSet = Substitute.For<IDbSet<Customer>>();
customerDbSet.Provider.Returns(customers.Provider);
customerDbSet.Expression.Returns(customers.Expression);
customerDbSet.ElementType.Returns(customers.ElementType);
customerDbSet.GetEnumerator().Returns(customers.GetEnumerator());

This provides us with an IDbSet<Customer> fake that we can return from an IRepositoryContext fake.

IRepositoryContext repositoryContext = Substitute.For<IRepositoryContext>();
repositoryContext.Customers.Returns(customerDbSet);

Finally, we are ready to assert our required test result (this example uses the MSTest framework bundled with Visual Studio).

decimal revenue = RevenueCalculator.Calculate(repositoryContext);

Assert.AreEqual(15, revenue);

To summarize, our entire test method looks like this

[TestMethod]
public void RevenueFrom3PremierCustomersWillBe15()
{
    /* Arrange */

    IQueryable<Customer> customers =
        new List<Customer>
        {
            new Customer { SubscriptionFee = 0 },
            new Customer { SubscriptionFee = 5 },
            new Customer { SubscriptionFee = 5 },
            new Customer { SubscriptionFee = 0 },
            new Customer { SubscriptionFee = 5 }
        }.AsQueryable();

    IDbSet<Customer> customerDbSet = Substitute.For<IDbSet<Customer>>();
    customerDbSet.Provider.Returns(customers.Provider);
    customerDbSet.Expression.Returns(customers.Expression);
    customerDbSet.ElementType.Returns(customers.ElementType);
    customerDbSet.GetEnumerator().Returns(customers.GetEnumerator());

    IRepositoryContext repositoryContext = Substitute.For<IRepositoryContext>();
    repositoryContext.Customers.Returns(customerDbSet);

    /* Act */

    decimal revenue = RevenueCalculator.Calculate(repositoryContext);

    /* Assert */

    Assert.AreEqual(15, revenue);
}

Run your unit test and if everything works out, you passed with flying colors. Now you know how to mock entity framework’s DbContext for unit testing. If not, double-check your code and try again. As we all know, software development can be a struggle at times.

If you are interested in .NET best practices, such as unit testing or abstracting Entity Framework code using the repository pattern, you may wish to check out Learning Tree’s .NET Best Practices and Design Patterns course.

Take Your .NET programming skills to the next level

View .NET training courses

Type to search blog.learningtree.com

Do you mean "" ?

Sorry, no results were found for your query.

Please check your spelling and try your search again.