skills/testing/integration-testing/SKILL.md
Use when writing integration tests with WebApplicationFactory, TestContainers, or test fixtures.
npx skillsauth add faysilalshareef/dotnet-ai-kit integration-testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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>data-ai
Use when about to claim work is complete, fixed, passing, or ready — before committing, creating PRs, or moving to the next task. Requires running verification commands and confirming output before making any success claims.
development
Use when encountering any bug, test failure, build error, or unexpected behavior — before proposing fixes or making changes.
development
Use when checkpointing, wrapping up, or handing off an AI-assisted development session.
development
Use when following the Specification-Driven Development lifecycle from plan through ship.