C
C#2mo ago
surwren

Seeding DB for Integration Test

I am currently using TestContainers and WebApplicationFactory and am able to successfully setup a basic test which returns successfully. I am following the InProcess Example I am thinking of improving the way I populate data. Currently I populate in Program.cs
using (var scope = app.Services.CreateScope()) {
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
dbContext.Database.Migrate();
var seeder = scope.ServiceProvider.GetRequiredService<DatabaseSeeder>();
seeder.SeedAsync().Wait();
}

app.Run();

public partial class Program { }
using (var scope = app.Services.CreateScope()) {
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
dbContext.Database.Migrate();
var seeder = scope.ServiceProvider.GetRequiredService<DatabaseSeeder>();
seeder.SeedAsync().Wait();
}

app.Run();

public partial class Program { }
public class DatabaseSeeder {
private readonly AppDbContext _context;
//... services
public DatabaseSeeder(AppDbContext context, IUserService userService, ...) {
//constructor assignment for services
}
public async Task SeedAsync()
{
if (!_context.Users.Any())
{
//seeding logic
}
}
}
public class DatabaseSeeder {
private readonly AppDbContext _context;
//... services
public DatabaseSeeder(AppDbContext context, IUserService userService, ...) {
//constructor assignment for services
}
public async Task SeedAsync()
{
if (!_context.Users.Any())
{
//seeding logic
}
}
}
2 Replies
surwren
surwrenOP2mo ago
From what I understand I can change my ControllerTests to something like this:
public class ControllerTests : IAsyncLifetime {
private readonly MsSqlContainer _msSqlContainer = new MsSqlBuilder().Build();
private readonly IServiceProvider _serviceProvider;

public ControllerTests() {
var services = new ServiceCollection();
services.AddSingleton(_msSqlContainer);
services.AddScoped<DatabaseSeeder>();
_serviceProvider = services.BuildServiceProvider();
}

public async Task InitializeAsync() {
await _msSqlContainer.StartAsync();

using (var scope = _serviceProvider.CreateScope()) {
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
dbContext.Database.Migrate();
var seeder = scope.ServiceProvider.GetRequiredService<DatabaseSeeder>();
await seeder.SeedAsync();
}
}
...
public class ControllerTests : IAsyncLifetime {
private readonly MsSqlContainer _msSqlContainer = new MsSqlBuilder().Build();
private readonly IServiceProvider _serviceProvider;

public ControllerTests() {
var services = new ServiceCollection();
services.AddSingleton(_msSqlContainer);
services.AddScoped<DatabaseSeeder>();
_serviceProvider = services.BuildServiceProvider();
}

public async Task InitializeAsync() {
await _msSqlContainer.StartAsync();

using (var scope = _serviceProvider.CreateScope()) {
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
dbContext.Database.Migrate();
var seeder = scope.ServiceProvider.GetRequiredService<DatabaseSeeder>();
await seeder.SeedAsync();
}
}
...
But is this the best way to do it? Do I need to inject a different service in just to seed? What are the common modern ways used for Integration tests?
Sossenbinder
Sossenbinder2mo ago
I wouldn't necessarily do it in the Program.cs of your actual app, because now you're unnecessarily introducing testing implementation details in your actual application Depending on your test framework there are usually some hooks where you can establish seeding routines I'd rely on those to be honest I think there's a tiered approach as to where: 1. If it's truly fundamental data, I'd to it wherever you initialize your TestContainer. You gotta make a decision if you want that part of your test to only be concerned with managing the TestContainer lifetime or whether it should also be concerned with details of the data you seed into the DB 2. If it's related to a group of tests, you could have some fixture or base class to just run the seeding implicitly for this category. That would decouple it from the testcontainer management as well 3. Test specific data should reside in e.g. IAsyncLifetimes initializer I think your question as to whether you need a different service just for seeding is totally up to you At the end of the day I'd just be careful not to introduce any of test-specific details into your actual app and execute or not execute them based on some flags (You could obviously also use EF core seeding, but I usually find it a bit difficult, having to scramble around the regular dbcontext registrations)

Did you find this page helpful?