Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resetting In-Memory database between integration tests

I've set up a project based on https://github.com/jasontaylordev/CleanArchitecture. But I'm having some troubles with writing my integration tests for the controllers, since the in-memory database does not reset between each test.Each test use a WebApplicationFactory in order to set up a test web server,

public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
                {
                    // Remove the app's ApplicationDbContext registration.
                    var descriptor = services.SingleOrDefault(
                        d => d.ServiceType ==
                            typeof(DbContextOptions<ApplicationDbContext>));

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

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

                    // Register test services
                    services.AddScoped<ICurrentUserService, TestCurrentUserService>();
                    services.AddScoped<IDateTime, TestDateTimeService>();
                    services.AddScoped<IIdentityService, TestIdentityService>();

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

                    // Create a scope to obtain a reference to the database
                    // context (ApplicationDbContext).
                    using (var scope = sp.CreateScope())
                    {
                        var scopedServices = scope.ServiceProvider;
                        var context = scopedServices.GetRequiredService<ApplicationDbContext>();
                        var logger = scopedServices.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();

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

                        try
                        {
                            // Seed the database with test data.
                            SeedSampleData(context);
                        }
                        catch (Exception ex)
                        {
                            logger.LogError(ex, "An error occurred seeding the database with test messages. Error: {Message}", ex.Message);
                        }
                    }
                })
                .UseEnvironment("Test");
        }
    ...

Where the following create test:

namespace CleanArchitecture.WebUI.IntegrationTests.Controllers.TodoItems
{
    public class Create : IClassFixture<CustomWebApplicationFactory<Startup>>
    {
        private readonly CustomWebApplicationFactory<Startup> _factory;

        public Create(CustomWebApplicationFactory<Startup> factory)
        {
            _factory = factory;
        }

        [Fact]
        public async Task GivenValidCreateTodoItemCommand_ReturnsSuccessCode()
        {
            var client = await _factory.GetAuthenticatedClientAsync();

            var command = new CreateTodoItemCommand
            {
                Title = "Do yet another thing."
            };

            var content = IntegrationTestHelper.GetRequestContent(command);

            var response = await client.PostAsync($"/api/todoitems", content);

            response.EnsureSuccessStatusCode();
        }
    ...

And the following read test:

namespace CleanArchitecture.WebUI.IntegrationTests.Controllers.TodoItems
{
    public class Read: IClassFixture<CustomWebApplicationFactory<Startup>>
    {
        private readonly CustomWebApplicationFactory<Startup> _factory;

        public Read(CustomWebApplicationFactory<Startup> factory)
        {
            _factory = factory;
        }

        [Fact]
        public async Task ShouldRetriveAllTodos()
        {
            var client = await _factory.GetAuthenticatedClientAsync();

            var response = await client.GetAsync($"/api/todoitems");

            var todos = Deserialize(response);
            todos.Count().Should().Be(1); //but is 2 because database is shared between read and create test class.
        }

The problem is that the in-memory database is not reset between each test. I've tried generating a different name for the in-memory database using Guid.New.ToString, but then the tests don't find the seeded database data, and by putting the tests in the same XUnit collection, to no avail.

Any good ideas how to make the tests not share database?

like image 609
TheFisherman Avatar asked Oct 16 '22 05:10

TheFisherman


1 Answers

What worked for me was to generate a DBName for each WebApplicationFactory instance, and I instantiate one of them for each test. So the test looks like this:

[Fact]
public void Test()
{
  // Arrange
  var appFactory = new WebApplicationFactory();
  // ...

  // Act
  // ...

  // Assert
  // ...
}

And the WebApplicationFactory:

    public class TestWebApplicationFactory : WebApplicationFactory<Startup>
    {
        private readonly string _dbName = Guid.NewGuid().ToString();

        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            base.ConfigureWebHost(builder);
            builder.ConfigureServices(services =>
            {
                services.AddDbContext<DbContext>(options =>
                {
                    // This is what makes a unique in-memory database per instance of TestWebApplicationFactory
                    options.UseInMemoryDatabase(_dbName);
                });
            });
        }
    }
like image 171
devcrp Avatar answered Nov 15 '22 04:11

devcrp