Integration testing with WebApplicationFactory, TestContainers, and test fixtures. Covers in-memory database setup, fixture sharing, and end-to-end test patterns. Trigger: integration test, WebApplicationFactory, TestContainers, fixture.
From dotnet-ai-kitnpx claudepluginhub faysilalshareef/dotnet-ai-kit --plugin dotnet-ai-kitThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
WebApplicationFactory<Program> creates an in-process test serverIClassFixture<T> shares expensive resources across testsConfigureTestServices for test-specific behaviornamespace {Company}.{Domain}.Tests.Integration;
public sealed class TestWebAppFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
// Replace real DB with in-memory
services.RemoveAll<DbContextOptions<ApplicationDbContext>>();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseInMemoryDatabase("TestDb_" + Guid.NewGuid()));
// Replace external services with fakes
services.RemoveAll<IServiceBusPublisher>();
services.AddSingleton<IServiceBusPublisher, FakeServiceBusPublisher>();
});
builder.UseEnvironment("Testing");
}
}
namespace {Company}.{Domain}.Tests.Integration;
public sealed class OrderIntegrationTests(TestWebAppFactory factory)
: IClassFixture<TestWebAppFactory>
{
[Fact]
public async Task CreateOrder_ShouldPersistAndReturnId()
{
// Arrange
using var scope = factory.Services.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
var command = new CreateOrderCommand("Test Customer", 100m, []);
// Act
var result = await mediator.Send(command);
// Assert
result.Id.Should().NotBeEmpty();
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var events = await db.Set<Event<OrderEventData>>()
.Where(e => e.AggregateId == result.Id)
.ToListAsync();
events.Should().HaveCount(1);
}
}
namespace {Company}.{Domain}.Tests.Integration;
public sealed class SqlServerFixture : IAsyncLifetime
{
private readonly MsSqlContainer _container = new MsSqlBuilder()
.WithImage("mcr.microsoft.com/mssql/server:2022-latest")
.Build();
public string ConnectionString => _container.GetConnectionString();
public async Task InitializeAsync()
{
await _container.StartAsync();
}
public async Task DisposeAsync()
{
await _container.DisposeAsync();
}
}
public sealed class RealDbWebAppFactory(SqlServerFixture sqlFixture)
: WebApplicationFactory<Program>, IClassFixture<SqlServerFixture>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
services.RemoveAll<DbContextOptions<ApplicationDbContext>>();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(sqlFixture.ConnectionString));
});
}
}
public sealed class DatabaseResetFixture : IAsyncLifetime
{
private Respawner _respawner = null!;
private string _connectionString = null!;
public async Task InitializeAsync()
{
_respawner = await Respawner.CreateAsync(_connectionString, new RespawnerOptions
{
TablesToIgnore = ["__EFMigrationsHistory"],
WithReseed = true
});
}
public async Task ResetAsync()
{
await _respawner.ResetAsync(_connectionString);
}
public Task DisposeAsync() => Task.CompletedTask;
}
[Fact]
public async Task CreateOrder_ViaGrpc_ShouldSucceed()
{
// Arrange
using var channel = GrpcChannel.ForAddress(
factory.Server.BaseAddress!,
new GrpcChannelOptions { HttpHandler = factory.Server.CreateHandler() });
var client = new OrderCommands.OrderCommandsClient(channel);
// Act
var response = await client.CreateOrderAsync(new CreateOrderRequest
{
CustomerName = "Test",
Total = 100.0
});
// Assert
response.OrderId.Should().NotBeNullOrEmpty();
response.Sequence.Should().Be(1);
}
| Anti-Pattern | Correct Approach |
|---|---|
| Shared mutable database state | Use Respawn or unique DB per test |
| Real external services in tests | Replace with fakes via ConfigureTestServices |
| Testing only happy paths | Include error cases and edge cases |
| Slow test startup | Use IClassFixture for shared resources |
# Find WebApplicationFactory
grep -r "WebApplicationFactory" --include="*.cs" tests/
# Find TestContainers
grep -r "TestcontainersBuilder\|MsSqlContainer" --include="*.cs" tests/
# Find IClassFixture
grep -r "IClassFixture" --include="*.cs" tests/
# Find Respawn
grep -r "Respawner\|Respawn" --include="*.cs" tests/
TestWebAppFactory — extend rather than creating newIClassFixture<T>