dotnet-integration-testing
Configures integration tests with WebApplicationFactory and Testcontainers. Provides test database setup, authentication helpers, and utilities for testing API endpoints with real dependencies.
git clone --depth 1 https://github.com/ronnythedev/dotnet-clean-architecture-skills /tmp/dotnet-integration-testing && cp -r /tmp/dotnet-integration-testing/skills/22-dotnet-integration-testing ~/.claude/skills/dotnet-integration-testingSKILL.md
# Integration Test Setup
## Overview
Integration tests verify the full request pipeline:
- **WebApplicationFactory** - In-memory test server
- **Testcontainers** - Real PostgreSQL in Docker
- **Respawn** - Fast database cleanup between tests
- **Authentication helpers** - Test with different users/roles
## Quick Reference
| Component | Purpose |
|-----------|---------|
| `IntegrationTestWebAppFactory` | Custom test server factory |
| `BaseIntegrationTest` | Base class for all tests |
| `Respawner` | Database cleanup utility |
| `TestAuthHandler` | Fake authentication handler |
---
## Test Project Structure
```
tests/
└── {name}.Api.IntegrationTests/
├── Infrastructure/
│ ├── IntegrationTestWebAppFactory.cs
│ ├── BaseIntegrationTest.cs
│ ├── TestAuthHandler.cs
│ └── FakeUserContext.cs
├── {Feature}/
│ ├── Create{Entity}Tests.cs
│ └── Get{Entity}Tests.cs
└── {name}.Api.IntegrationTests.csproj
```
---
## Template: Test Project File
```xml
<!-- tests/{name}.Api.IntegrationTests/{name}.Api.IntegrationTests.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Respawn" Version="6.1.0" />
<PackageReference Include="Testcontainers.PostgreSql" Version="3.6.0" />
<PackageReference Include="xunit" Version="2.6.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\{name}.api\{name}.api.csproj" />
<ProjectReference Include="..\..\src\{name}.infrastructure\{name}.infrastructure.csproj" />
</ItemGroup>
</Project>
```
---
## Template: WebApplicationFactory
```csharp
// tests/{name}.Api.IntegrationTests/Infrastructure/IntegrationTestWebAppFactory.cs
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Testcontainers.PostgreSql;
using {name}.infrastructure;
namespace {name}.Api.IntegrationTests.Infrastructure;
public class IntegrationTestWebAppFactory
: WebApplicationFactory<Program>, IAsyncLifetime
{
private readonly PostgreSqlContainer _dbContainer = new PostgreSqlBuilder()
.WithImage("postgres:15-alpine")
.WithDatabase("testdb")
.WithUsername("postgres")
.WithPassword("postgres")
.Build();
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
// ═══════════════════════════════════════════════════════════════
// REPLACE DATABASE WITH TEST CONTAINER
// ═══════════════════════════════════════════════════════════════
services.RemoveAll(typeof(DbContextOptions<ApplicationDbContext>));
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseNpgsql(_dbContainer.GetConnectionString());
});
// ═══════════════════════════════════════════════════════════════
// REPLACE AUTHENTICATION WITH TEST HANDLER
// ═══════════════════════════════════════════════════════════════
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = TestAuthHandler.SchemeName;
options.DefaultChallengeScheme = TestAuthHandler.SchemeName;
})
.AddScheme<TestAuthSchemeOptions, TestAuthHandler>(
TestAuthHandler.SchemeName,
options => { });
// ═══════════════════════════════════════════════════════════════
// REPLACE EXTERNAL SERVICES WITH FAKES
// ═══════════════════════════════════════════════════════════════
// services.RemoveAll<IEmailService>();
// services.AddSingleton<IEmailService, FakeEmailService>();
});
builder.UseEnvironment("Testing");
}
public async Task InitializeAsync()
{
await _dbContainer.StartAsync();
// Apply migrations
using var scope = Services.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
await dbContext.Database.MigrateAsync();
}
public new async Task DisposeAsync()
{
await _dbContainer.StopAsync();
}
}
```
---
## Template: Test Authentication Handler
```csharp
// tests/{name}.Api.IntegrationTests/Infrastructure/TestAuthHandler.cs
using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace {name}.Api.IntegrationTests.Infrastructure;
public class TestAuthSchemeOptions : AuthenticationSchemeOptions
{
public Guid? UserId { get; set; }
public string? Email { get; set; }
public string[]? Roles { get; set; }
public string[]? Permissions { get; set; }
}
public class TestAuthHandler : AuthenticationHandler<TestAuthSchemeOptions>
{
public const string SchemeName = "TestScheme";
public const string TestUserIdHeader = "X-Test-User-Id";
public const string TestUserEmailHeader = "X-Test-User-Email";Scaffolds a complete .NET solution following Clean Architecture principles with proper layer separation (API, Application, Domain, Infrastructure). Creates project structure, dependency injection setup, and cross-cutting concerns configuration.
Generates CQRS Commands with Handlers, Validators, and Request DTOs following Clean Architecture patterns. Commands represent actions that modify state and return Result types for proper error handling.
Generates CQRS Queries with Handlers and Response DTOs for read operations. Uses Dapper for optimized read queries, bypassing the domain model for better performance.
Generates Domain Entities following DDD principles with factory methods, private setters, domain events, and proper encapsulation. Supports aggregate roots, child entities, and value objects.
Generates Repository interfaces and implementations following the Repository pattern. Provides data access abstraction for aggregate roots with EF Core implementations.
Generates Entity Framework Core configurations using Fluent API. Maps domain entities to database tables with proper relationships, constraints, and conventions.
Generates RESTful API Controllers with proper routing, versioning, authorization, and MediatR integration. Follows REST conventions and Clean Architecture patterns.
Generates Minimal API endpoints following Microsoft's recommended approach. Creates fast, testable HTTP APIs with minimal code using MapGet/MapPost/MapPut/MapDelete. Preferred over controller-based APIs for new projects.