How to seed test data using WebApplicationFactory?

Viewed 1367

I'm working on integration tests for .NET Core 3.1 Web API. I followed this article Painless Integration Testing with ASP.NET Core Web API. This is my CustomWebApplicationFactory:

public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<TestDbContext>));
            if (descriptor != null)
            {
                services.Remove(descriptor);
            }

            services.AddDbContext<TestDbContext>(options =>
            {
                options.UseInMemoryDatabase("InMemoryTestDb");
            });

            var sp = services.BuildServiceProvider();

            using (var scope = sp.CreateScope())
            {
                var scopedServices = scope.ServiceProvider;
                var db = scopedServices.GetRequiredService<TestDbContext>();
                var logger = scopedServices.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();

                db.Database.EnsureCreated();

                try
                {
                    Console.WriteLine("Seeding...");

                    var players = Utilities.GetTestPlayers(); // Returns 3 test players
                    db.Players.RemoveRange(db.Players); // Clear the table
                    db.Players.AddRange(players); // Add 3 test players
                    db.SaveChanges();

                    Console.WriteLine($"Players seeded: { db.Players.Count() }");
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, "An error occurred seeding the " + "database with test messages. Error: {Message}", ex.Message);
                }
            }
        });
    }
}

These are my tests: For each Controller I created tests in separate class. In the constructor of test class I create a Client. Like this for TestController (I have total 4 Controllers in the API):

public class BaseControllerTest : IClassFixture<CustomWebApplicationFactory<Startup>>
{
    protected HttpClient Client { get; }

    public BaseControllerTest(CustomWebApplicationFactory<Startup> factory)
    {
        Client = factory.CreateClient();
    }
}

public class TestControllerShould : BaseControllerTest
{
    public TestControllerShould(CustomWebApplicationFactory<Startup> factory)
        : base(factory)
    {
        Console.WriteLine("TestControllerShould");
    }

    [Fact]
    public async Task GetHelloWorld()
    {
        var request = new HttpRequestMessage(new HttpMethod("GET"), "/test/");
        var response = await Client.SendAsync(request);

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var content = await response.Content.ReadAsStringAsync();
        Assert.Equal("Hello World!", content);
        Assert.False(response.Headers.Contains("Server"), "Should not contain server header");
    }
} 

My problem is:

As you can see in the CustomWebApplicationFactory I'm logging while seeding, and this is the log I see:

Seeding...
Seeding...
Seeding...
Seeding...
Players seeded: 12
Players seeded: 12
Players seeded: 12
Players seeded: 12

I have total of 4 Controllers and I'm seeding only 3 players. Why Am I seeing 12? I don't understand. It looks like line db.Players.AddRange(players); is called 4 times (for each Controller test class) before any db.SaveChanges();. Then db.SaveChanges(); is called 4 times in a row. Why?

My question is: Why is that? and How to properly seed test data in integration tests?

(if not by answer, any reference to a nice article will be greatly appreciated)

Thank you

1 Answers

It calls ConfigureServices for every controller. So here you can do one thing before calling db.Database.EnsureCreated() call db.Database.EnsureDeleted(). It will Re initialize the In memory database for every controller.

Related