Application testing
.NET Core C# Entity Framework Core NUnit SQL TDD Visual Studio

How to Unit Test .NET Core Applications with In-Memory Data Providers

Welcome to today’s post.

The topic of this post is in-memory providers in .NET Core applications and their use when unit testing.

In a previous post, I showed how to structure an NUnit test suite to provide unit testing for applications that use Entity Framework Core as a data context. Did you also now that we can override the data context that is configured within the .NET Core startup? The ability to override the data context configuration allows us to use an in-memory data provider without affecting the data that is within the SQL data provider.

I will start this discussion by looking at when and when we should avoid using in-memory providers, and the benefits and disadvantages of using them.

When to use In-Memory Data Providers

The use of in-memory providers is most valuable when implementing unit tests and a connection to a cut-down database that mirrors the schema of the physical SQL database.

In a pre .NET Core environment (.NET Framework 4.6 and before), we would have to make use of any number of third-party providers that were installed from NuGet to provide in-memory functionality.

When is the best scenario to use in-memory data providers when testing?

In-memory data providers are more useful when we want to create tests with mock data using the same database schema as our test or production databases and an ORM provider such as Entity Framework Core.  

One benefit of using in-memory data providers is that we don’t need to tear down any infrastructure after testing is completed. The memory itself would free itself after the test running with the application closes.

One scenario where they can be usefully accommodated is within a suite of running unit tests and validation of many business rules within a development build integration task within a DevOps pipeline. The results of the unit tests would then be reported to the pipeline project team and developers.

When are in-memory data providers not so useful?

The following scenarios are not suitable for in-memory unit testing:

  1. Using an existing test database for more realistic real-world test data OR
  2. Use a higher quantity of data to perform stress testing OR
  3. Integration testing using large databases.

Setup of In-Memory Providers within a .NET Core Unit Test Project

I will now explain how to utilize the in-memory provider for .NET core.

With .NET core, there are no additional NuGet packages or external libraries required to use the in-memory feature after installing the Entity Framework Core package. We just include the namespace Microsoft.EntityFrameworkCore.

To use the in-memory provider in your unit test class, declare the namespace:

using Microsoft.EntityFrameworkCore.InMemory

To setup the in-memory provider, you will need to add a data context to your application’s ServiceCollection as follows:

services.AddDbContext<ApplicationDbContext>(options =>
    options.UseInMemoryDatabase("In_Memory_Db")
);

Where:

ApplicationDbContext is your database context.

In_Memory_Db is the name of your in-memory database.

To retrieve the context of your in-memory data store, we can use the following code snip:

serviceProvider = services.BuildServiceProvider();
context = serviceProvider.GetRequiredService
   <ApplicationDbContext>();

The library namespace references we require are as follows:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.InMemory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;

The variables we require to setup our test fixture are as follows:

private ServiceCollection services;
private DbContextOptions<ApplicationDbContext> opts;
private ApplicationDbContext context;
private ServiceProvider serviceProvider;

The above allows us to implement the following setup for the test fixture:

[SetUp]
public void Setup()
{
      services = new ServiceCollection();

      opts = new DbContextOptionsBuilder<ApplicationDbContext>()
            .UseInMemoryDatabase(databaseName: "In_Memory_Db")
            .Options;
      services.AddDbContext<ApplicationDbContext>(options =>
      options.UseInMemoryDatabase("In_Memory_Db"));

      serviceProvider = services.BuildServiceProvider();
      context = serviceProvider.GetRequiredService<ApplicationDbContext>();

      services.AddTransient<IBookService, BookService>();
      …
}

The above setup code is like the setup of IOC container dependencies with ASP.NET Core within the startup configuration.

Implementation of Unit Tests with In-Memory Providers

Using the in-memory provider through the data context is straightforward. Below is an example of data insertion and testing the data count:

var book = new BookViewModel() { 
    ID = 1, 
    Author = "W.Smith", 
    Title = "Rivers Run Dry", 
    YearPublished = 1954 
};
context.Add(book);
context.SaveChanges();

int bookCount = context.Books
    .CountAsync()
    .GetAwaiter()
    .GetResult();

var result = books.GetResult();
Assert.AreEqual(1, result.Count);

We can implement a significant number of data related unit tests with the in-memory data provider, however where the in-memory data provider may not provide us with testing coverage is with code that uses SQL objects directly, including the following:

  1. Stored procedure calls.
  2. Raw SQL queries.
  3. Database DDL operations.

This is expected, even with unit test frameworks such as Moq or NMock we cannot avoid having to mock out code that depend on SQL data object access. Where our code base does not reference SQL data directly, we can maximize test coverage fully using the Entity Framework Core ORM provider. All unit tests for SQL objects should then be moved into an integration test project as they are external dependencies.

The main benefit of the in-memory data provider is allowing our data dependent codebase where possible to be refactored and increase its test coverage.

That’s all for today’s post.

I hope this post has been informative and useful.

Social media & sharing icons powered by UltimatelySocial