A while ago I ran into an issue while trying to write some integration tests for a .NET Core based API. The API was using EF Core and the idea was to replace this with an inmemory database in the integration tests.

I followed the otherwise excellent documentation from Microsoft on how to write proper integration tests using the WebApplicationFactory to generate a test host.

https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1

Unfortunately I ran into an issue. For some reason whenever I ran the test it executed with the real database connection specified in the Startup.cs rather than the one I had overridden in my WebApplicationFactory.

It turns out that other people had this issue to. As seen in this issue its not a bug – its missing update of the documentation of how to do this properly.

https://github.com/dotnet/aspnetcore/issues/13918#issuecomment-532162945

There is a link to some other comments on how to solve this, but no clear description of the exact solution.

What I ended up doing was implementing a simple method CleanupDatabaseRegistrations that removes the services registrations from the Startup class related to Entity Framework and then follow the before mentioned guide on registering an inmemory database for the unit tests.

using System;
using System.Linq;
using DGSDashboardService.Infrastructure;
using DGSDashboardService.Tests.Infrastructure.Interfaces;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace DGSDashboardService.Tests.Infrastructure
{
    public class TestHost : WebApplicationFactory<Startup>
    {
        private readonly ISeedDataProvider _seedDataProvider;

        public TestHost(ISeedDataProvider seedDataProvider)
        {
            _seedDataProvider = seedDataProvider;
        }

        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                // Remove database registrations
                // https://github.com/dotnet/aspnetcore/issues/13918#issuecomment-532162945
                CleanupDatabaseRegistrations<AppDbContext>(services);

                // Create a new service provider.
                var serviceProvider = new ServiceCollection()
                    .AddEntityFrameworkInMemoryDatabase()
                    .BuildServiceProvider();

                // Add a database context (AppDbContext) using an in-memory database for testing.
                services.AddDbContext<AppDbContext>(options =>
                {
                    options.UseInMemoryDatabase("IntegrationTests");
                    options.UseInternalServiceProvider(serviceProvider);
                });

                // BuildDetails the service provider.
                var sp = services.BuildServiceProvider();

                // Create a scope to obtain a reference to the database contexts
                using var scope = sp.CreateScope();
                var scopedServices = scope.ServiceProvider;
                var appDb = scopedServices.GetRequiredService<AppDbContext>();

                var logger = scopedServices.GetRequiredService<ILogger<TestHost>>();

                // Ensure the database is created.
                appDb.Database.EnsureCreated();

                try
                {
                    // Seed the database with some specific test data.
                    _seedDataProvider?.PopulateTestData(appDb);
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, "An error occurred seeding the " +
                                        "database with test messages. Error: {ex.Message}");
                }
            });
        }

        /// <summary>
        /// Cleanup EF Core service registrations
        /// </summary>
        /// <typeparam name="TDbContext"></typeparam>
        /// <param name="services"></param>
        private void CleanupDatabaseRegistrations<TDbContext>(IServiceCollection services) where TDbContext : DbContext
        {
            var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<TDbContext>));
            if (descriptor != null)
            {
                services.Remove(descriptor);
            }

            descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions));
            if (descriptor != null)
            {
                services.Remove(descriptor);
            }

            descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(TDbContext));
            if (descriptor != null)
            {
                services.Remove(descriptor);
            }

        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *