Unit Testing EF7

Unit Testing EF7

Entity Framework 7 comes with a new provider, an in memory database provider. This is a huge win for unit testing because now you can test the exact same data access code you use in production but on a fast, totally isolated, zero setup, data store. This article will explain how to get started unit testing an ASP.NET 5 / EF7 application, using the InMemoryDatabase.

Image Credit: Les Haines

Say we have a controller like this that we want to unit test:

[Route("api/v1/[controller]")]
public class CustomersController : Controller
{
    private MyDbContext db;

    public CustomersController(MyDbContext db)
    {
        this.db = db;
    }

    [HttpGet("{id}")]
    public CustomerDto Get(int id)
    {
        return this.db.Customers
            .Include(x => x.Company)
            .Include(x => x.Phones)
            .Include(x => x.Addresses)
            .ThenInclude(y => y.AdrsType)
            .Single(x => x.CustomerID == id)
            .ToDto();
    }
}

In the past this could have been a big headache because there is EF data access code right there in the controller. In order to test this, you would have had to mock out an entire DbContext which is no easy task. This lead many people to using the repository pattern so that they could test their applications without hitting the database. One problem with this approach is that, for CRUD style applications you end up mocking the exact thing you are trying to test.

Enter the InMemoryDatabase provider

During testing, we can pass in the exact same MyDbContext class that we do in production, we just simply configure it differently. Instead of using, for instance, the SqlServerDatabase provider we use the InMemoryDatabase provider. The benefit here is that we are running our tests against something that is a very close simulation of the real database. All of the EF based validations and constraints will still be enforced, but the test will be very fast because everything is in memory. Moreover, there is no hassle of setting up and maintaining a test database instance somewhere.

To get started you need to take a dependency on EntityFramework.InMemory. To do this, add it to your project.json file.

{
    ...
    "dependencies": {
        "EntityFramework.InMemory": "7.0.0-beta6",
        "EntityFramework.Relational": "7.0.0-beta6",
        "EntityFramework.Core": "7.0.0-beta6",
        ...
    },
    ...
}

Then make sure that your DbContext takes a DbContextOptions parameter and passes that on to the base class so that you can configure it differently at test time.

public class MyDbContext : DbContext
{
    public MyDbContext()
    {
    }

    public MyDbContext(DbContextOptions options)
        : base(options)
    {
    }

    ...
}

Finally, just pass in the DbContextOptions to your context at test time, making sure to configure it using the UseInMemoryDatabase extension method.

...
using Microsoft.Data.Entity; // Gets you the UseInMemoryDatabase extension method
...

public class TestCustomersController
{
    private CustomersController sut;

    public TestCustomersController()
    {
        var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();

        // This is the magic line
        optionsBuilder.UseInMemoryDatabase(persist: false);

        var db = new MyDbContext(optionsBuilder.Options);
        this.sut = new CustomersController(db);

        db.Customers.AddRange(
            new Customer
            {
                AccountNo = "42",
                CustomerID = 94,
                PersonFName = "Enzo",
                PersonLName = "Matrix",
                CustomerPhones = new List<CustomerPhone>
                {
                    new CustomerPhone
                    {
                        AreaCode = "123",
                        CustomerID = 94,
                        CustomerPhoneID = 12,
                        CustomerPhoneNo = "867-5309",
                        Extension = "5",
                        IsPrimary = false,
                    },
                    new CustomerPhone
                    {
                        AreaCode = "555",
                        CustomerID = 94,
                        CustomerPhoneID = 35,
                        CustomerPhoneNo = "0118 999 881 999 119 725 3",
                        Extension = "6",
                        IsPrimary = true,
                    }
                },
            },
            new Customer
            {
                AccountNo = "8675309",
                CustomerID = 27,
                PersonFName = "Dot",
                PersonLName = "Matrix",
            }
        );

        db.SaveChanges();
    }

    [Fact]
    public void GetCanRetrieveCustomer()
    {
        var c = this.sut.Get(94);
        Assert.NotNull(c);
    }
}

Summary

In summary, the InMemoryDatabase that ships with Entity Framework 7 can make testing CRUD style applications much easier. Moreover, the code can become simpler because programmers need not implement 100 layers of abstraction just to get their code into a test harness.