Ish Thomas
Ish Thomas

Reputation: 2440

How to seed test data using WebApplicationFactory?

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

Upvotes: 3

Views: 2953

Answers (1)

Tech Yogesh
Tech Yogesh

Reputation: 447

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.

Upvotes: 2

Related Questions