ASP.NET Core integration tests with xUnit, WebApplicationFactory and Testcontainers PostgreSQL

xUnit Testing & CI/CD in .NET November 13, 2026 csharp

Integration tests with WebApplicationFactory and Testcontainers, against a real PostgreSQL.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// ======================================================================
// Q: How do you test an API that uses PostgreSQL?
//
// Integration tests with WebApplicationFactory and Testcontainers, against a
// real PostgreSQL. Not the InMemory provider: it doesn't check constraints,
// transactions or SQL.
// An authorization test like the one above is exactly what AI won't write on its
// own. Mentioning it earns you points.
// ======================================================================

public class ApiFactory : WebApplicationFactory<Program>, IAsyncLifetime
{
    private readonly PostgreSqlContainer _pg = new PostgreSqlBuilder()
        .WithImage("postgres:16-alpine").Build();

    protected override void ConfigureWebHost(IWebHostBuilder b) =>
        b.ConfigureTestServices(s =>
        {
            s.RemoveAll<DbContextOptions<AppDbContext>>();
            s.AddDbContext<AppDbContext>(o => o.UseNpgsql(_pg.GetConnectionString()));
        });

    public async Task InitializeAsync()
    {
        await _pg.StartAsync();
        using var scope = Services.CreateScope();
        await scope.ServiceProvider.GetRequiredService<AppDbContext>().Database.MigrateAsync();
    }
    public new Task DisposeAsync() => _pg.DisposeAsync().AsTask();
}

public class OrdersTests(ApiFactory f) : IClassFixture<ApiFactory>
{
    [Fact]
    public async Task Customer_cannot_read_other_customers_order()
    {
        var client = f.CreateClientAs(customerId: 1);   // helper with a test auth handler
        var res = await client.GetAsync("/api/orders/999"); // belongs to customer 2
        Assert.Equal(HttpStatusCode.NotFound, res.StatusCode);
    }
}