Saturday, May 9, 2020

ASP.NET Core Integration Tests: Different Database for Each Test

It's a common requirement when writing Integration Tests, the test data to be isolated, so we are not interfering with other tests. And also, for some tests, you can use an InMemory database, but in some cases, you might require an actual SQL Server database. In this post, let's see how we can use a different database for each Integration Test in ASP.NET Core. 

Please note the data access technology is EF Core.

ASP.NET Core provides a nice set of building blocks through Microsoft.AspNetCore.Mvc.Testing to efficiently write Integrated Tests for all kinds of ASP.NET Core Web Applications. There is a very detailed documentation page at Integration tests in ASP.NET Core which describes all the important aspects, so I am not going to write about those.

So I have a custom TestWebApplicationFactory deriving from WebApplicationFactory<TEntryPoint>. And there when Configuring the WebHost, I am expecting a builder setting for TestDatabaseType. And based on that, I am configuring the database context.
public class TestWebApplicationFactory : WebApplicationFactory<Startup>
{
    private static readonly InMemoryDatabaseRoot _inMemoryDatabaseRoot = new InMemoryDatabaseRoot();

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        string _inMemoryConnectionString = "<InMemoryConnectionString>";
        string _sqliteConnectionString = "<SqliteConnectionString>";
        string _sqlConnectionString = "<SqlServerConnectionString>";

        builder.ConfigureServices(services =>
        {
            if (!Enum.TryParse(builder.GetSetting(TestConstants.TestDatabaseTypeKey), out TestDatabaseType testDatabaseType))
            {
                testDatabaseType = TestDatabaseType.InMemory;
            }

            ServiceDescriptor descriptor = services
                .SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<ApplicationCoreDbContext>));

            if (descriptor != null)
            {
                services.Remove(descriptor);
            }

            services.AddDbContext<ApplicationCoreDbContext>(options =>
            {
                switch (testDatabaseType)
                {
                    case TestDatabaseType.InMemory:
                        options.UseInMemoryDatabase($"{_inMemoryConnectionString}", _inMemoryDatabaseRoot);
                        break;
                    case TestDatabaseType.Sqlite:
                        options.UseSqlite(_sqliteConnectionString);
                        break;
                    case TestDatabaseType.SqlServer:
                        options.UseSqlServer(_sqlConnectionString);
                        break;
                }
            }); // other setup
        });
    }
}
and from the tests, when I am creating the HttpClient, I am just setting up a builder setting to specify which database to use.
public class UsersControllerTests : IClassFixture<TestWebApplicationFactory>
{
    private readonly TestWebApplicationFactory _factory;

    public UsersControllerTests(TestWebApplicationFactory factory)
    {
        _factory = factory;
    }

    [Fact]
    public async Task TestOne()
    {
        HttpClient client = _factory.WithWebHostBuilder(builder =>
            {
                builder.UseSetting(TestConstants.TestDatabaseTypeKey, TestDatabaseType.InMemory.ToString()); // other setup
            })
            .CreateClient();
    }

    [Fact]
    public async Task TestTwo()
    {
        HttpClient client = _factory.WithWebHostBuilder(builder =>
            {
                builder.UseSetting(TestConstants.TestDatabaseTypeKey, TestDatabaseType.SqlServer.ToString()); // other setup
            })
            .CreateClient();
    }
}
It's pretty easy, right!

Happy Coding.

Regards,
Jaliya

No comments:

Post a Comment