April 2017

Volume 32 Number 4

[Data Points]

Tips for Building Tests with EF Core and Its InMemory Provider

By Julie Lerman

Julie LermanWhen creating automated tests against methods that trigger database interaction, there are times when you truly want to see what’s happening in the database, and other times when the database interaction isn’t at all relevant to your test’s assertion. The new EF Core InMemory provider can prove useful in the latter case. In this article I’ll provide an introduction to this handy tool and share some tips and tricks about creating automated tests with EF Core that I’ve discovered while learning to use it myself.

In cases where the database effort isn’t important to the test result, unnecessary calls to the database can put a strain on performance or even cause inaccurate test results. For example, the amount of time it takes to talk to the database—or drop and recreate a test database—can hold up your tests. Another concern is if there’s a problem with the database itself. Perhaps network latency or a momentary hiccup causes a test to fail only because the database is unavailable, not as a result of a failure in the logic the test is attempting to assert.

We’ve long sought ways to minimize these side effects. Fakes and mocking frameworks are common solutions. These patterns allow you to create in-memory representations of the data store, but there’s a lot involved in setting up their in-memory data and behavior. Another approach is to use a lighter-weight database for testing than you’re targeting in production, such as a PostgreSQL or SQLite database, rather than, for example, a SQL Server database you use for your production data store. Entity Framework (EF) has always allowed for targeting different databases with a single model thanks to the various providers available. However, nuanced differences in database functionality can cause you to hit issues where this won’t always work (though it’s still a good option to keep in your toolbox). Alternatively, you could use an external tool, such as the open source EFFORT extension (github.com/tamasflamich/effort), which magically provides an in-memory representation of the data store without the setup needed for fakes or mocking frameworks. EFFORT works with EF 4.1 through EF6, but not EF Core.

There are already a number of database providers for EF Core. Microsoft includes the SQL Server and SQLite providers as part of the family of EntityFrameworkCore APIs. There are also providers for SQLCE and PostgreSQL, respectively maintained by MVPs Erik Eilskov Jensen and Shay Rojansky. And there are third-party commercially available providers. But Microsoft has created another provider—not to persist to a database, but to temporarily persist to memory. This is the InMemory provider: Microsoft. EntityFrameworkCore.InMemory, which you can use as a quick way to provide a stand-in for an actual database in many testing scenarios.

Readying the DbContext for the InMemory Provider

Because your DbContext will sometimes be used to connect to a true data store and sometimes to the InMemory provider, you want to set it up to be flexible with respect to providers, rather than dependent on any particular provider.

When instantiating a DbContext in EF Core, you have to include DbContextOptions that specify which provider to use and, if needed, a connection string. UseSqlServer and UseSqlite, for example, require that you pass in a connection string, and each provider gives you access to the relevant extension method. Here’s how this looks if done directly in the DbContext class OnConfiguring method, where I’m reading a connection string from an application config file:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
  var settings = ConfigurationManager.ConnectionStrings;
  var connectionString = settings["productionDb"].ConnectionString;
  optionsBuilder.UseSqlServer(connectionString);
 }

A more flexible pattern, however, is to pass a pre-configured DbContextOptions object into the constructor of the DbContext:

public SamuraiContext(DbContextOptions<SamuraiContext> options)
    :base(options) { }

EF Core will pass those pre-configured options into the underlying DbContext and apply them for you.

With this constructor in place, you now have a way to specify different providers (and other options, such as a connection string) on the fly from the logic that’s using the context.

If you’re using some type of Inversion of Control (IoC) container in your application, such as StructureMap (structuremap.github.io) or the services built into ASP.NET Core, you have the ability to configure the provider for the context in the code where you configure other application-wide IoC services. Here’s an example that uses ASP.NET Core services in a typical startup.cs file:

public void ConfigureServices(IServiceCollection services) {
  services.AddDbContext<SamuraiContext>(
    options => options.UseSqlServer(
      Configuration.GetConnectionString("productionDb")));
  services.AddMvc();
}

In this case, SamuraiContext is the name of my class that inherits from DbContext. I’m using SQL Server again and I’ve stored the connection string in the ASP.NET Core appsettings.json file under the name productionDb. The service has been configured to know that any time a class constructor requires an instance of Samurai­Context, the runtime should not only instantiate Samurai­Context, but should pass in the options with the provider and connection string stated in this method.

When this ASP.NET Core app uses my SamuraiContext, by default, it will now do so with SQL Server and my connection string. But thanks to the flexibility I built into the SamuraiContext class, I can also create tests that use the same SamuraiContext but pass in a DbContextOptions object that specifies using the InMemory provider instead—or specifies any other options that are relevant to a particular test.

In the next section I’ll show two different tests that involve EF Core. The first, Figure 1, is designed to test that the correct database interaction occurred. This means I truly want the test to hit the database, so I’ll construct DbContextOptions to use the SQL Server provider, but with a connection string that targets a test version of my database I can create and drop on the fly.

Figure 1 Testing That a Database Insert Works as Expected

[TestMethod]
  public void CanInsertSamuraiIntoDatabase() {
    var optionsBuilder = new DbContextOptionsBuilder();
    optionsBuilder.UseSqlServer
      ("Server = (localdb)\\mssqllocaldb; Database =
        TestDb; Trusted_Connection = True; ");
    using (var context = new SamuraiContext(optionsBuilder.Options)) {
      context.Database.EnsureDeleted();
      context.Database.EnsureCreated();
      var samurai = new Samurai();
      context.Samurais.Add(samurai);
      var efDefaultId = samurai.Id;
      context.SaveChanges();
      Assert.AreNotEqual(efDefaultId, samurai.Id);
    }
  }

I use the EnsureDeleted and EnsureCreated methods to give me a totally fresh version of the database for the test, and these will work even if you don’t have migrations. Alternatively, you could use EnsureDeleted and Migrate to recreate the database if you have migration files.

Next, I create a new entity (samurai), tell EF to begin tracking it and then note the temporary key value the SQL Server provider supplies. After calling SaveChanges, I verify that SQL Server has applied its own database-generated value for the key, assuring me that this object was, indeed, inserted into the database correctly.

Deleting and recreating a SQL Server database might affect how long it takes to run the test. You could use SQLite in this case and get the same results more quickly while ensuring that the test is still hitting an actual database. Also, note that, like the SQL Server provider, SQLite also sets a temporary key value when you add an entity to the context.

If you have methods that happen to use EF Core but you want to test them without hitting the database, this is where the InMemory provider is so handy. But keep in mind that InMemory is not a database and won’t emulate all flavors of relational database behavior—for example, referential integrity. When this is important to your test, you may prefer the SQLite option or, as the EF Core docs suggest, the SQLite in-memory mode as explained at bit.ly/2l7M71p.

Here’s a method I’ve written in an app that performs a query with EF Core and returns a list of KeyValuePair objects:

public List<KeyValuePair<int, string>> GetSamuraiReferenceList() {
  var samurais = _context.Samurais.OrderBy(s => s.Name)
    .Select(s => new {s.Id, s.Name})
    .ToDictionary(t => t.Id, t => t.Name).ToList();
  return samurais;
}

I want to test that the method truly returns a KeyValuePair list. I don’t need it to query the database in order to prove this.

Following is a test to do that using the InMemory provider (which I’ve already installed into the test project):

[TestMethod]
  public void CanRetrieveListOfSamuraiValues() {
    _options = new DbContextOptionsBuilder<SamuraiContext>()
               .UseInMemoryDatabase().Options;
    var context = new SamuraiContext(_options);
    var repo = new DisconnectedData(context);
    Assert.IsInstanceOfType(repo.GetSamuraiReferenceList(),
                            typeof(List<KeyValuePair<int, string>>));
  }

This test doesn’t even require any sample data to be available to the in-memory representation of the database because it’s enough to return an empty list of KeyValuePairs. When I run the test, EF Core will be sure that when the GetSamuraiReferenceList executes its query, the provider will allocate resources in memory for EF to execute against. The query is successful and so is the test.

What if I want to test that the correct number of results are returned? This means I’ll need to provide data to seed the InMemory provider. Much like a fake or mock, this requires creating the data and loading it into the provider’s data store. When using fakes and mocks, you might create a List object and populate that, then query against the list. The InMemory provider takes care of the container. You just use EF commands to pre-populate it. The InMemory provider also takes care of much of the overhead and extra coding that are needed when using fakes or mocks.

As an example, Figure 2 shows a method I’m using to seed the InMemory provider before my tests interact with it:

Figure 2 Seeding an EF Core InMemory Provider

private void SeedInMemoryStore() {
    using (var context = new SamuraiContext(_options)) {
      if (!context.Samurais.Any()) {
        context.Samurais.AddRange(
          new Samurai {
            Id = 1,
            Name = "Julie",
          },
          new Samurai {
            Id = 2,
            Name = "Giantpuppy",
        );
        context.SaveChanges();
      }
    }
  }

If my in-memory data is empty, this method adds in two new samurais and then calls SaveChanges. Now it’s ready to be used by a test.

But how would my InMemory data store have data in it if I’ve just instantiated the context? The context is not the InMemory data store. Think of the data store as a List object—the context will create it on the fly, if needed. But once it’s been created, it remains in memory for the lifetime of the application. If I’m running a single test method, there will be no surprises. But if I’m running a number of test methods, then every test method will use the same set of data and you may not want to populate it a second time. There’s more to understand about this, which I’ll be able to explain after you see a bit more code.

This next test is a bit contrived, but it’s designed to demonstrate using a populated InMemory store. Knowing that I’ve just seeded the memory with two samurais, the test calls that same GetSamuraiReferenceList method and asserts that there are two items in the resulting list:

[TestMethod]
  public void CanRetrieveAllSamuraiValuePairs() {
    var context = new SamuraiContext(_options);
    var repo = new DisconnectedData(context);
    Assert.AreEqual(2, repo.GetSamuraiReferenceList().Count);
  }

You may have noticed that I didn’t call the seed method or create the options. I’ve moved that logic to the test class constructor so I don’t have to repeat it in my tests. The _options variable is declared for the full scope of the class:

private DbContextOptions<SamuraiContext> _options;
  public TestDisconnectedData() {
    _options =
      new DbContextOptionsBuilder<SamuraiContext>().UseInMemoryDatabase().Options;
    SeedInMemoryStore();
  }

Now that I’ve moved the seed method into the constructor, you might think (as I did) that it will get called only once. But that’s not the case. Did you know that a test class constructor gets hit by every test method that’s run? In all honesty, I had forgotten this until I noticed that tests were passing when run on their own, but failing when I ran them together. That was before I added in the check to see if the samurai data already existed in memory. Every method that triggered the seed method to be called would be seeding the same collection. This would happen whether I was calling the seed method in each test method or only once in the constructor. The check for pre-existing data protects me either way.

There’s a nicer way to avoid the problem of conflicting in-memory data stores. InMemory allows you to provide a name for its data store.

If you want to move the creation of the DbContextOptions back into the test method, and do so for each method, specifying a unique name as a parameter of UseInMemory will assure that each method is using its own data store.

I refactored my test class by removing the class-wide _options variable and the class constructor. Instead, I use a method for creating the options for a named data store and seeding the particular data store that takes in the desired name as a parameter:

private DbContextOptions<SamuraiContext> SetUpInMemory(string uniqueName) {
  var options = new DbContextOptionsBuilder<SamuraiContext>()
                    .UseInMemoryDatabase(uniqueName).Options;
  SeedInMemoryStore(options);
  return options;
}

I modified the signature and first line of the SeedInMemoryStore to use the configured options for the unique data store:

private void SeedInMemoryStore(DbContextOptions<SamuraiContext> options) {
  using (var context = new SamuraiContext(options)) {

And each test method now uses this method along with a unique name to instantiate the DbContext. Here’s the revised CanRetrieve­AllSamuraiValuePairs. The only change is that I’m now passing in the new SetUpInMemory method along with the unique data store name. A nice pattern recommended by the EF team is to use the test name as the name of the InMemory resource:

[TestMethod]
  public void CanRetrieveListOfSamuraiValues() {
  using (var context = 
      new SamuraiContext(SetUpInMemory("CanRetrieveListOfSamuraiValues"))) {
    var repo = new DisconnectedData(context);
    Assert.IsInstanceOfType(repo.GetSamuraiReferenceList(),
                            typeof(List<KeyValuePair<int, string>>));
   }
 }

Other test methods in my test class have their own unique data store names. And now you see there are patterns for using a unique set of data, or sharing a common set of data across test methods. When your tests are writing data to the in-memory data store, the unique names allow you to avoid side effects on other tests. Keep in mind that EF Core 2.0 will always require a name to be supplied, as opposed to the optional parameter in EF Core 1.1.

There’s one last tip I want to share about the InMemory data store. In writing about the first test, I pointed out that both the SQL Server and SQLite providers insert a temporary value to the Samurai’s key property when the object is added to the context. I didn’t mention that if you specify the value yourself, the provider won’t overwrite that. But in either case, because I’m using the default database behavior, the database overwrites the value with its own generated primary key value. With the InMemory provider, however, if you supply a key property value, that will be the value that the data store uses. If you don’t supply one, the InMemory provider uses a client-side key generator whose value acts as the data-store assigned value.

The samples I’ve used come from my EF Core: Getting Started course on Pluralsight (bit.ly/PS_EFCoreStart), where you can learn more about EF Core, as well as testing with EF Core. The sample code is also included as a download with this article.


Julie Lerman is a Microsoft Regional Director, Microsoft MVP, software team mentor and consultant who lives in the hills of Vermont. You can find her presenting on data access and other topics at user groups and conferences around the world. She blogs at thedatafarm.com/blog and is the author of “Programming Entity Framework,” as well as a Code First and a DbContext edition, all from O’Reilly Media. Follow her on Twitter: @julielerman and see her Pluralsight courses at juliel.me/PS-Videos.

Thanks to the following Microsoft technical expert for reviewing this article: Rowan Miller


Discuss this article in the MSDN Magazine forum