Entity Framework Core In Memory Testing

Scott Brady
Scott Brady
Entity Framework ・ Updated January 2023

When writing tests, you don’t always want to use a physical database, instead opting for an in-memory solution. Whatever your reasons for doing this, with the release of Entity Framework Core, you now have a couple of different options recommended by the EF team when testing using in-memory databases.

Your two choices for in-memory database providers depend on whether or not you’re using the Microsoft.EntityFrameworkCore.Relational package and if you need the full behavior of a relational database during testing.

If you don’t need relational behavior, you can use the Entity Framework Core in-memory database provider (Microsoft.EntityFrameworkCore.InMemory). If you do need it, you can use the SQLite database provider (Microsoft.EntityFrameworkCore.Sqlite) and SQLite’s in-memory mode.

Testing Entity Framework Core with the in-memory database provider

The InMemory provider is sold as something that "approximates connecting to the real database, without the overhead of actual database operations", with the caveat that it is not suitable for mimicking a relational database.

To use the in-memory database provider first you need the required nuget package:

dotnet add package Microsoft.EntityFrameworkCore.InMemory

In order to create an instance of a DbContext to use for our tests, you must first create an instance of DbContextOptions. By default, a DbContext has a constructor parameter of either DbContextOptions or DbContextOptions<TContext>. I typically prefer the generic version as it allows multiple DbContext types to be used at once.

To create an instance of DbContextOptions outside of the your DI container, you can use the DbContextOptionsBuilder class with the usual call to UseInMemoryDatabase.

var builder = new DbContextOptionsBuilder<TestDbContext>();
builder.UseInMemoryDatabase("test_db");
DbContextOptions<TestDbContext> options = builder.Options;

Once the builder is configured to use the in-memory database provider, you can then use its Options property to get your configured instance of DbContextOptions. You can then pass this DbContextOptions into the constructor of our DbContext for testing.

You always need to create a named instance of an in-memory database. This can help prevent collisions.

Here’s an example using a test DbContext.

var testEntity = new TestEntity { Id = 1, Name = "Test Entity" };
await using (var context = new TestDbContext(options))
{
    context.TestEntities.Add(testEntity);
    await context.SaveChangesAsync();
}

TestEntity foundEntity;
await using (var context = new TestDbContext(options))
{
    foundEntity = await context.TestEntities.FirstOrDefaultAsync(x => x.Id == testEntity.Id);
}

Assert.NotNull(foundEntity);
Assert.Equal(testEntity.Name, foundEntity.Name);

Testing Entity Framework Core relational databases using SQLite in-memory mode

The SQLite provider is a relational database provider but you can take advantage of SQLite in-memory mode. The nice thing about this provider is that you get the full behavior of a relational database, with the benefits of the running in-memory.

First, you’ll need the Entity Framework Core Sqlite nuget package:

dotnet add package Microsoft.EntityFrameworkCore.Sqlite

You’ll then need to configure a DbContextOptions for use with Sqlite and its in-memory mode. This is a little more involved than the in-memory database provider:

var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" };
var connectionString = connectionStringBuilder.ToString();
var connection = new SqliteConnection(connectionString);

var builder = new DbContextOptionsBuilder<TestDbContext>();
builder.UseSqlite(connection);
DbContextOptions<TestDbContext> options = builder.Options;

await using (var context = new TestDbContext(options))
{
    await context.Database.OpenConnectionAsync();
    await context.Database.EnsureCreatedAsync();
}

Here you are configuring a new SqliteConnection via the SqliteConnectionStringBuilder class. By using using this you avoid an exception when trying to use ":memory:" as a connection string (an ArgumentException: "Format of the initialization string does not conform to specification starting at index 0").

You then pass this SqliteConnection into your UseSqlite extension method.

Another step required for the SQLite provider is to call both the OpenConnection and EnsureCreated methods on the context’s DatabaseFacade. Otherwise you are met with a DbUpdateException: "SQLite Error 1: 'no such table: TestEntites'".

Once you have created your dbcontext options, you can pass them to your DbContext, in the same way as the previous example.

By default each SQLite connection using the ":memory:" connection string will use a different in-memory database (the opposite of the entity framework core in-memory database provider). To override this behavior check out the 'In-memory Databases And Shared Cache' section of the SQLite in-memory databases documentation.

Do not use only in-memory testing for your Entity Framework Core tests. As you can see from the providers in this article: every provider is different, so make sure you write integration tests that target your provider of choice.

Source Code

You can find a working example of the above code on GitHub.