Entity Framework Core – Unit Testing

For easier unit testing, Entity Framework Core offers a memory-based povider. You can use the same context you use with SQL Server (or other providers) with the memory-based provider.
This article explains how you can configure Entity Framework Core to use the memory-based provider for unit testing.

Testing

This article is not about why unit testing is great, it just gives the information how to remove the dependency on a database with Entity Framework Core by using a memory-based provider – a provider that is here for unit tests.

Creating the Project To Be Tested

First, let’s create a ASP.NET Core Web application. I’m starting with the empty template and just add a few features for an API controller.

For the sample application I’m using Visual Studio 2017 RC with the .NET Core Tools MSBuild “alpha” and .NET Core 1.1. You can easily use Visual Studio 2015 Update 3 with .NET Core 1.0 as well. The features shown here are also available for Entity Framework Core 1.0.

The Book type defines the model that will be used from the EF Core context:

public class Book
{
    public int BookId { get; set; }
    public string Title { get; set; }
    public string Publisher { get; set; }
}

The BooksContext implements the EF Core context and is prepared for dependency injection by using the constructor DbContextOptions:

public class BooksContext : DbContext
{
    public BooksContext(DbContextOptions<BooksContext> options)
        : base(options) { }
    public DbSet<Book> Books { get; set; }
}

With the BooksController, the EF context is injected in the constructor, and methods such as Get make use of this context:

[Route("api/[controller]")]
public class BooksController : Controller
{
    private readonly BooksContext _booksContext;
    public BooksController(BooksContext booksContext)
    {
        _booksContext = booksContext;
    }
    // GET: api/values
    [HttpGet]
    public IEnumerable<Book> Get() => _booksContext.Books;

    // GET api/values/5
    [HttpGet("{id}")]
    public Book Get(int id) => _booksContext.Books.Find(id);

    //...

Previously I explained that the sample code can be used with Entity Framework Core 1.0 as well. Theres one exception: the Find method of the DbSet type is new with EF Core 1.1. In case you dont want to use Visual Studio 2017 yet, you can also use EF Core 1.1 with Vsiual Studio 2015 – or use the SingleOrDefault method instead of the Find method.

Lastly, the context and MVC is added to the DI container. The EF context is configured to use the SQL Server provider.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<BooksContext>(options =>      
        options.UseSqlServer(@"server=(localdb)\mssqllocaldb;database=BooksSample;trusted_connection=true")
    );
    services.AddMvc();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    //...  

    app.UseMvcWithDefaultRoute();

    //...
}

The packages that need to be added to the project for using EF Core with SQL Server are

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.SqlServer

Creating the Unit Test Project

To create a unit test project, Visual Studio 2017 offers the project template xUnit Test Project (.NET Core). This project template already includes references to the Microsoft.NET.Test.Sdk and xunit NuGet packages:

  • Microsoft.NET.Test.Sdk
  • xunit
  • xunit.runner.visualstudio

To test the Web application, I’m adding a reference to the previously created Web application, and the NuGet package for the in-memory EF Core provider:

  • Microsoft.EntityFrameworkCore.InMemory
  • Microsoft.Extensions.DependencyInjection

If you use can’t use Visual Studio 2017 today, with Visual Studio 2015, to use xUnit for unit testing of .NET Core applications, you can create a Class Library (.NET Core) and add the needed NuGet packages and tools to project.json.

Let’s get into the unit testing class. In the test class the InitContext method, the BooksContext is instantiated. BooksContext needs DbContextOptions passed to the constructor. In the Web application, a DI container was created to inject the EF Context into the controller, and for use by several MVC services. Here, the BooksContext is created explicitly by creating a new instance, thus a DI container is not required for this feature. However, DbContextOptions are needed. The options can be created using an DbContextOptionsBuilder. Creating the DbContextOptionsBuilder, the EF context type is passed as a generic parameter. With this builder, the extension method UseInMemoryDatabase registers the extension InMemoryOptionsExtension to the options. This finally allows passing the configuration to the constructor of the EF context.

EF Core internally also uses a DI container where services are registered for initialization, queries, building the model, navigation, logging, and more.

After creating the EF Core context, sample data is created and added to the context. Calling the SaveChanges method, the data is permanently written to the transient store.

Enumerable.Range offers an easy way to create test data.

public class BooksControllerTest
{
    public BooksControllerTest()
    {
        InitContext();
    }

    private BooksContext _booksContext;

    public void InitContext()
    {
        var builder = new DbContextOptionsBuilder<BooksContext>()
            .UseInMemoryDatabase();

        var context = new BooksContext(builder.Options);
        var books = Enumerable.Range(1, 10)
            .Select(i => new Book { BookId = i, Title = $"Sample{i}", Publisher = "Wrox Press" });
        context.Books.AddRange(books);
        int changed = context.SaveChanges();
        _booksContext = context;
    }

    //...
}

The method that is tested is the Get method of the BooksController. The BooksController constructor receives the EF Core context that was created before. Other than that, there’s nothing special on this unit test.

[Fact]
public void TestGetBookById()
{
    string expectedTitle = "Sample2";
    var controller = new BooksController(_booksContext);
    Book result = controller.Get(2);
    Assert.Equal(expectedTitle, result.Title);
}

With this in place, you can run the unit test from the Visual Studio 2017 Test Explorer.

Test Explorer

Summary

Entity Framework Core makes it easy to create unit tests without the need to build a separate implementation of the context. The provider can be switched easily to use a memory-based provider which comes as part of EF Core, and use this one for unit tests.

Sample Code

The sample code is available at GitHub.

Have fun with programming and learning!
Christian

More Information

More information about Entity Framework Core is available in my new book and my C# workshops:

Professional C# 6 and .NET Core 1.0

Christian Nagel’s Workshops

Image from © Bradcalkins | Dreamstime.com Test Photo

8 thoughts on “Entity Framework Core – Unit Testing

  1. Is this really a unit test? You are technically testing the in memory provider too. If you only use an interface for your dbcontext you would avoid all of this. If there ends up being a bug in the memory version, the unit test will fail. this is really an integration test that doesn’t really help you.

    Like

  2. Even I think its not unit test , but there is no other option of testing the line

    public IEnumerable Get() => _booksContext.Books;
    the statment booksContext.Books is very difficult to moq in case of entityfranework

    but i found this very helpful

    Like

    1. sulabh, thanks you found it helpful 🙂
      Anyway, you need a mocking class. The in-memory database provider was created for unit tests.
      Would it be a “unit test” if UseInMemoryDatabase would be named UseMockingContext?

      Like

      1. Why do you need an in memory database for a unit test? You’d be mocking the data returned by the context directly, bypassing the need for the in-memory version to the layer above. If you need to test the queries, that is what an integration test is for. Sometimes, EF isn’t good enough and you have to write custom SQL. How will you test those scenarios with an in memory db? It doesn’t run SQL.

        Like

  3. I have one problem because the persistence of the data sent an error in add range, because exist an element with the same id.

    I found on internet to use this statament
    _context.Database.EnsureDeleted();

    for avoid the data persistence error.

    Like

  4. hello, why do you need a big purple band, almost as wide as your content column (no matter how wide you make the browser window) that extends down the full length of your page and makes your content difficult to read?

    Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.