Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Seed test data for every test in ASP.NET Core / EF Core / xUnit.NET integration tests

I've been following the strategies for setting up tests for an ASP.NET Core 2.2 API using the Microsoft documentation at Integration tests in ASP.NET Core.

To summarize, we extend and customize WebApplicationFactory and use a IWebHostBuilder to setup and configure various services to provide us with a database context using an in-memory database for testing like below (copied and pasted from the article):

public class CustomWebApplicationFactory<TStartup> 
    : WebApplicationFactory<TStartup> where TStartup: class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            // Create a new service provider.
            var serviceProvider = new ServiceCollection()
                .AddEntityFrameworkInMemoryDatabase()
                .BuildServiceProvider();

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

            // 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 db = scopedServices.GetRequiredService<ApplicationDbContext>();
                var logger = scopedServices
                    .GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();

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

                try
                {
                    // Seed the database with test data.
                    Utilities.InitializeDbForTests(db);
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, $"An error occurred seeding the " +
                        "database with test messages. Error: {ex.Message}");
                }
            }
        });
    }
}

In the tests we can use the factory and create a client like so:

public class IndexPageTests : 
    IClassFixture<CustomWebApplicationFactory<RazorPagesProject.Startup>>
{
    private readonly HttpClient _client;
    private readonly CustomWebApplicationFactory<RazorPagesProject.Startup> 
        _factory;

    public IndexPageTests(
        CustomWebApplicationFactory<RazorPagesProject.Startup> factory)
    {
        _factory = factory;
        _client = factory.CreateClient(new WebApplicationFactoryClientOptions
            {
                AllowAutoRedirect = false
            });
    }

    [Fact]
    public async Task Test1()
    {
        var response = await _client.GetAsync("/api/someendpoint");
    }
}

This works fine, but note the call to InitializeDbForTests which sets up some test data for all tests when the services are configured.

I'd like a reasonable strategy for starting every API test with a clean slate, so that tests don't become dependent on each other. I've been looking for various ways to get a hold of ApplicationDbContext in my test methods to no avail.

Would it be reasonable to do integration tests in complete isolation from each other, and how could I approach it using ASP.NET Core / EF Core / xUnit.NET?

like image 771
Victor Sand Avatar asked Apr 23 '19 12:04

Victor Sand


1 Answers

Ironically, you're looking for EnsureDeleted instead of EnsureCreated. That will dump the database. Since the in-memory "database" is schemaless, you don't actually need to ensure it is created or even migrate it.

Additionally, you should not be using a hard-coded name for the in-memory database. That will actually cause the same database instance in-memory to be used everywhere. Instead, you should use something random: Guid.NewGuid().ToString() is good enough.

like image 166
Chris Pratt Avatar answered Nov 14 '22 23:11

Chris Pratt