integration-testing

Integration Test Setup

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "integration-testing" with this command: npx skills add ronnythedev/dotnet-clean-architecture-skills/ronnythedev-dotnet-clean-architecture-skills-integration-testing

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

<!-- 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

// 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&#x3C;ApplicationDbContext>));

        services.AddDbContext&#x3C;ApplicationDbContext>(options =>
        {
            options.UseNpgsql(_dbContainer.GetConnectionString());
        });

        // ═══════════════════════════════════════════════════════════════
        // REPLACE AUTHENTICATION WITH TEST HANDLER
        // ═══════════════════════════════════════════════════════════════
        
        services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = TestAuthHandler.SchemeName;
            options.DefaultChallengeScheme = TestAuthHandler.SchemeName;
        })
        .AddScheme&#x3C;TestAuthSchemeOptions, TestAuthHandler>(
            TestAuthHandler.SchemeName,
            options => { });

        // ═══════════════════════════════════════════════════════════════
        // REPLACE EXTERNAL SERVICES WITH FAKES
        // ═══════════════════════════════════════════════════════════════
        
        // services.RemoveAll&#x3C;IEmailService>();
        // services.AddSingleton&#x3C;IEmailService, FakeEmailService>();
    });

    builder.UseEnvironment("Testing");
}

public async Task InitializeAsync()
{
    await _dbContainer.StartAsync();
    
    // Apply migrations
    using var scope = Services.CreateScope();
    var dbContext = scope.ServiceProvider.GetRequiredService&#x3C;ApplicationDbContext>();
    await dbContext.Database.MigrateAsync();
}

public new async Task DisposeAsync()
{
    await _dbContainer.StopAsync();
}

}

Template: Test Authentication Handler

// 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"; public const string TestUserRolesHeader = "X-Test-User-Roles"; public const string TestUserPermissionsHeader = "X-Test-User-Permissions";

public TestAuthHandler(
    IOptionsMonitor&#x3C;TestAuthSchemeOptions> options,
    ILoggerFactory logger,
    UrlEncoder encoder)
    : base(options, logger, encoder)
{
}

protected override Task&#x3C;AuthenticateResult> HandleAuthenticateAsync()
{
    // Check for test headers
    if (!Request.Headers.TryGetValue(TestUserIdHeader, out var userIdHeader))
    {
        return Task.FromResult(AuthenticateResult.NoResult());
    }

    if (!Guid.TryParse(userIdHeader, out var userId))
    {
        return Task.FromResult(AuthenticateResult.Fail("Invalid user ID"));
    }

    var claims = new List&#x3C;Claim>
    {
        new(ClaimTypes.NameIdentifier, userId.ToString()),
        new("sub", userId.ToString())
    };

    // Add email
    if (Request.Headers.TryGetValue(TestUserEmailHeader, out var emailHeader))
    {
        claims.Add(new Claim(ClaimTypes.Email, emailHeader.ToString()));
        claims.Add(new Claim("email", emailHeader.ToString()));
    }

    // Add roles
    if (Request.Headers.TryGetValue(TestUserRolesHeader, out var rolesHeader))
    {
        foreach (var role in rolesHeader.ToString().Split(','))
        {
            claims.Add(new Claim(ClaimTypes.Role, role.Trim()));
        }
    }

    // Add permissions
    if (Request.Headers.TryGetValue(TestUserPermissionsHeader, out var permissionsHeader))
    {
        foreach (var permission in permissionsHeader.ToString().Split(','))
        {
            claims.Add(new Claim("permission", permission.Trim()));
        }
    }

    var identity = new ClaimsIdentity(claims, SchemeName);
    var principal = new ClaimsPrincipal(identity);
    var ticket = new AuthenticationTicket(principal, SchemeName);

    return Task.FromResult(AuthenticateResult.Success(ticket));
}

}

Template: Base Integration Test

// tests/{name}.Api.IntegrationTests/Infrastructure/BaseIntegrationTest.cs using System.Net.Http.Headers; using System.Net.Http.Json; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Npgsql; using Respawn; using {name}.infrastructure;

namespace {name}.Api.IntegrationTests.Infrastructure;

[Collection("Integration")] public abstract class BaseIntegrationTest : IAsyncLifetime { protected readonly IntegrationTestWebAppFactory Factory; protected readonly HttpClient Client; protected readonly IServiceScope Scope; protected readonly ApplicationDbContext DbContext;

private static Respawner? _respawner;
private static string? _connectionString;

protected BaseIntegrationTest(IntegrationTestWebAppFactory factory)
{
    Factory = factory;
    Client = factory.CreateClient();
    Scope = factory.Services.CreateScope();
    DbContext = Scope.ServiceProvider.GetRequiredService&#x3C;ApplicationDbContext>();
}

// ═══════════════════════════════════════════════════════════════
// AUTHENTICATION HELPERS
// ═══════════════════════════════════════════════════════════════

/// &#x3C;summary>
/// Configure client to authenticate as a specific user
/// &#x3C;/summary>
protected void AuthenticateAs(
    Guid userId,
    string email = "test@example.com",
    string[]? roles = null,
    string[]? permissions = null)
{
    Client.DefaultRequestHeaders.Add(TestAuthHandler.TestUserIdHeader, userId.ToString());
    Client.DefaultRequestHeaders.Add(TestAuthHandler.TestUserEmailHeader, email);

    if (roles?.Length > 0)
    {
        Client.DefaultRequestHeaders.Add(
            TestAuthHandler.TestUserRolesHeader,
            string.Join(",", roles));
    }

    if (permissions?.Length > 0)
    {
        Client.DefaultRequestHeaders.Add(
            TestAuthHandler.TestUserPermissionsHeader,
            string.Join(",", permissions));
    }
}

/// &#x3C;summary>
/// Configure client to authenticate as admin
/// &#x3C;/summary>
protected void AuthenticateAsAdmin()
{
    AuthenticateAs(
        userId: Guid.NewGuid(),
        email: "admin@example.com",
        roles: new[] { "Admin" },
        permissions: new[] { "users:read", "users:write", "users:delete" });
}

/// &#x3C;summary>
/// Remove authentication headers
/// &#x3C;/summary>
protected void RemoveAuthentication()
{
    Client.DefaultRequestHeaders.Remove(TestAuthHandler.TestUserIdHeader);
    Client.DefaultRequestHeaders.Remove(TestAuthHandler.TestUserEmailHeader);
    Client.DefaultRequestHeaders.Remove(TestAuthHandler.TestUserRolesHeader);
    Client.DefaultRequestHeaders.Remove(TestAuthHandler.TestUserPermissionsHeader);
}

// ═══════════════════════════════════════════════════════════════
// HTTP HELPERS
// ═══════════════════════════════════════════════════════════════

protected async Task&#x3C;HttpResponseMessage> GetAsync(string url)
{
    return await Client.GetAsync(url);
}

protected async Task&#x3C;T?> GetAsync&#x3C;T>(string url)
{
    var response = await Client.GetAsync(url);
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadFromJsonAsync&#x3C;T>();
}

protected async Task&#x3C;HttpResponseMessage> PostAsync&#x3C;T>(string url, T content)
{
    return await Client.PostAsJsonAsync(url, content);
}

protected async Task&#x3C;TResponse?> PostAsync&#x3C;TRequest, TResponse>(string url, TRequest content)
{
    var response = await Client.PostAsJsonAsync(url, content);
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadFromJsonAsync&#x3C;TResponse>();
}

protected async Task&#x3C;HttpResponseMessage> PutAsync&#x3C;T>(string url, T content)
{
    return await Client.PutAsJsonAsync(url, content);
}

protected async Task&#x3C;HttpResponseMessage> DeleteAsync(string url)
{
    return await Client.DeleteAsync(url);
}

// ═══════════════════════════════════════════════════════════════
// DATABASE HELPERS
// ═══════════════════════════════════════════════════════════════

/// &#x3C;summary>
/// Add entity directly to database for test setup
/// &#x3C;/summary>
protected async Task AddAsync&#x3C;TEntity>(TEntity entity) where TEntity : class
{
    DbContext.Set&#x3C;TEntity>().Add(entity);
    await DbContext.SaveChangesAsync();
}

/// &#x3C;summary>
/// Get entity from database
/// &#x3C;/summary>
protected async Task&#x3C;TEntity?> FindAsync&#x3C;TEntity>(Guid id) where TEntity : class
{
    return await DbContext.Set&#x3C;TEntity>().FindAsync(id);
}

/// &#x3C;summary>
/// Execute raw SQL for test setup
/// &#x3C;/summary>
protected async Task ExecuteSqlAsync(string sql, object? parameters = null)
{
    await DbContext.Database.ExecuteSqlRawAsync(sql);
}

// ═══════════════════════════════════════════════════════════════
// LIFECYCLE
// ═══════════════════════════════════════════════════════════════

public async Task InitializeAsync()
{
    // Initialize Respawner once
    if (_respawner is null)
    {
        _connectionString = DbContext.Database.GetConnectionString();
        
        await using var connection = new NpgsqlConnection(_connectionString);
        await connection.OpenAsync();

        _respawner = await Respawner.CreateAsync(connection, new RespawnerOptions
        {
            DbAdapter = DbAdapter.Postgres,
            SchemasToInclude = new[] { "public" },
            TablesToIgnore = new Respawn.Graph.Table[] 
            { 
                "__EFMigrationsHistory"  // Don't reset migrations table
            }
        });
    }

    // Reset database before each test
    await using var conn = new NpgsqlConnection(_connectionString);
    await conn.OpenAsync();
    await _respawner.ResetAsync(conn);
}

public Task DisposeAsync()
{
    Scope.Dispose();
    return Task.CompletedTask;
}

}

/// <summary> /// Collection definition for integration tests /// Ensures tests share the same WebApplicationFactory /// </summary> [CollectionDefinition("Integration")] public class IntegrationTestCollection : ICollectionFixture<IntegrationTestWebAppFactory> { }

Template: Integration Tests

// tests/{name}.Api.IntegrationTests/{Feature}/Create{Entity}Tests.cs using System.Net; using System.Net.Http.Json; using FluentAssertions; using {name}.Api.IntegrationTests.Infrastructure; using {name}.application.{feature}.Create{Entity};

namespace {name}.Api.IntegrationTests.{Feature};

public class Create{Entity}Tests : BaseIntegrationTest { public Create{Entity}Tests(IntegrationTestWebAppFactory factory) : base(factory) { }

// ═══════════════════════════════════════════════════════════════
// SUCCESS TESTS
// ═══════════════════════════════════════════════════════════════

[Fact]
public async Task Create_Should_ReturnCreated_When_ValidRequest()
{
    // Arrange
    AuthenticateAsAdmin();

    var request = new Create{Entity}Request
    {
        Name = "Test Entity",
        Description = "Test Description",
        OrganizationId = Guid.NewGuid()
    };

    // Act
    var response = await PostAsync("/api/v1/{entities}", request);

    // Assert
    response.StatusCode.Should().Be(HttpStatusCode.Created);

    var entityId = await response.Content.ReadFromJsonAsync&#x3C;Guid>();
    entityId.Should().NotBeEmpty();

    // Verify in database
    var entity = await FindAsync&#x3C;Domain.{Aggregate}.{Entity}>(entityId);
    entity.Should().NotBeNull();
    entity!.Name.Should().Be(request.Name);
}

[Fact]
public async Task Create_Should_ReturnLocationHeader_When_Created()
{
    // Arrange
    AuthenticateAsAdmin();

    var request = new Create{Entity}Request
    {
        Name = "Test Entity",
        Description = "Test Description",
        OrganizationId = Guid.NewGuid()
    };

    // Act
    var response = await PostAsync("/api/v1/{entities}", request);

    // Assert
    response.Headers.Location.Should().NotBeNull();
    response.Headers.Location!.ToString().Should().Contain("/api/v1/{entities}/");
}

// ═══════════════════════════════════════════════════════════════
// VALIDATION TESTS
// ═══════════════════════════════════════════════════════════════

[Fact]
public async Task Create_Should_ReturnBadRequest_When_NameIsEmpty()
{
    // Arrange
    AuthenticateAsAdmin();

    var request = new Create{Entity}Request
    {
        Name = string.Empty,
        Description = "Test Description",
        OrganizationId = Guid.NewGuid()
    };

    // Act
    var response = await PostAsync("/api/v1/{entities}", request);

    // Assert
    response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}

[Fact]
public async Task Create_Should_ReturnBadRequest_When_NameTooLong()
{
    // Arrange
    AuthenticateAsAdmin();

    var request = new Create{Entity}Request
    {
        Name = new string('a', 101),  // Exceeds 100 char limit
        Description = "Test Description",
        OrganizationId = Guid.NewGuid()
    };

    // Act
    var response = await PostAsync("/api/v1/{entities}", request);

    // Assert
    response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}

// ═══════════════════════════════════════════════════════════════
// AUTHENTICATION TESTS
// ═══════════════════════════════════════════════════════════════

[Fact]
public async Task Create_Should_ReturnUnauthorized_When_NotAuthenticated()
{
    // Arrange
    RemoveAuthentication();

    var request = new Create{Entity}Request
    {
        Name = "Test Entity",
        Description = "Test Description",
        OrganizationId = Guid.NewGuid()
    };

    // Act
    var response = await PostAsync("/api/v1/{entities}", request);

    // Assert
    response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}

// ═══════════════════════════════════════════════════════════════
// AUTHORIZATION TESTS
// ═══════════════════════════════════════════════════════════════

[Fact]
public async Task Create_Should_ReturnForbidden_When_NoWritePermission()
{
    // Arrange
    AuthenticateAs(
        userId: Guid.NewGuid(),
        permissions: new[] { "entities:read" });  // Only read permission

    var request = new Create{Entity}Request
    {
        Name = "Test Entity",
        Description = "Test Description",
        OrganizationId = Guid.NewGuid()
    };

    // Act
    var response = await PostAsync("/api/v1/{entities}", request);

    // Assert
    response.StatusCode.Should().Be(HttpStatusCode.Forbidden);
}

// ═══════════════════════════════════════════════════════════════
// CONFLICT TESTS
// ═══════════════════════════════════════════════════════════════

[Fact]
public async Task Create_Should_ReturnConflict_When_NameAlreadyExists()
{
    // Arrange
    AuthenticateAsAdmin();

    // Create first entity
    var firstRequest = new Create{Entity}Request
    {
        Name = "Duplicate Name",
        Description = "First entity",
        OrganizationId = Guid.NewGuid()
    };
    await PostAsync("/api/v1/{entities}", firstRequest);

    // Try to create second entity with same name
    var secondRequest = new Create{Entity}Request
    {
        Name = "Duplicate Name",  // Same name
        Description = "Second entity",
        OrganizationId = Guid.NewGuid()
    };

    // Act
    var response = await PostAsync("/api/v1/{entities}", secondRequest);

    // Assert
    response.StatusCode.Should().Be(HttpStatusCode.Conflict);
}

}

Template: Get Entity Tests

// tests/{name}.Api.IntegrationTests/{Feature}/Get{Entity}Tests.cs using System.Net; using System.Net.Http.Json; using FluentAssertions; using {name}.Api.IntegrationTests.Infrastructure; using {name}.application.{feature}.Get{Entity}ById;

namespace {name}.Api.IntegrationTests.{Feature};

public class Get{Entity}Tests : BaseIntegrationTest { public Get{Entity}Tests(IntegrationTestWebAppFactory factory) : base(factory) { }

[Fact]
public async Task GetById_Should_ReturnEntity_When_Exists()
{
    // Arrange
    AuthenticateAsAdmin();

    var entityId = await CreateTestEntity();

    // Act
    var response = await GetAsync($"/api/v1/{entities}/{entityId}");

    // Assert
    response.StatusCode.Should().Be(HttpStatusCode.OK);

    var result = await response.Content.ReadFromJsonAsync&#x3C;{Entity}Response>();
    result.Should().NotBeNull();
    result!.Id.Should().Be(entityId);
}

[Fact]
public async Task GetById_Should_ReturnNotFound_When_NotExists()
{
    // Arrange
    AuthenticateAsAdmin();
    var nonExistentId = Guid.NewGuid();

    // Act
    var response = await GetAsync($"/api/v1/{entities}/{nonExistentId}");

    // Assert
    response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}

[Fact]
public async Task GetAll_Should_ReturnPaginatedList()
{
    // Arrange
    AuthenticateAsAdmin();

    // Create multiple entities
    await CreateTestEntity("Entity 1");
    await CreateTestEntity("Entity 2");
    await CreateTestEntity("Entity 3");

    // Act
    var response = await GetAsync("/api/v1/{entities}?pageNumber=1&#x26;pageSize=10");

    // Assert
    response.StatusCode.Should().Be(HttpStatusCode.OK);

    var result = await response.Content.ReadFromJsonAsync&#x3C;PagedListResponse>();
    result.Should().NotBeNull();
    result!.Items.Should().HaveCount(3);
    result.TotalCount.Should().Be(3);
}

// ═══════════════════════════════════════════════════════════════
// HELPER METHODS
// ═══════════════════════════════════════════════════════════════

private async Task&#x3C;Guid> CreateTestEntity(string name = "Test Entity")
{
    var request = new
    {
        Name = name,
        Description = "Test Description",
        OrganizationId = Guid.NewGuid()
    };

    var response = await PostAsync("/api/v1/{entities}", request);
    response.EnsureSuccessStatusCode();

    return await response.Content.ReadFromJsonAsync&#x3C;Guid>();
}

}

public class PagedListResponse { public List<{Entity}Response> Items { get; set; } = new(); public int PageNumber { get; set; } public int PageSize { get; set; } public int TotalCount { get; set; } }

Template: Test Utilities

// tests/{name}.Api.IntegrationTests/Infrastructure/TestDataSeeder.cs using {name}.infrastructure;

namespace {name}.Api.IntegrationTests.Infrastructure;

public static class TestDataSeeder { public static async Task SeedOrganizationAsync( ApplicationDbContext context, Guid organizationId, string name = "Test Organization") { var organization = new { Id = organizationId, Name = name, CreatedAtUtc = DateTime.UtcNow };

    await context.Database.ExecuteSqlRawAsync(
        @"INSERT INTO organizations (id, name, created_at_utc) 
          VALUES ({0}, {1}, {2})",
        organizationId,
        name,
        DateTime.UtcNow);
}

public static async Task SeedUserAsync(
    ApplicationDbContext context,
    Guid userId,
    string email,
    Guid organizationId)
{
    await context.Database.ExecuteSqlRawAsync(
        @"INSERT INTO users (id, email, name, organization_id, is_active, created_at_utc) 
          VALUES ({0}, {1}, {2}, {3}, true, {4})",
        userId,
        email,
        "Test User",
        organizationId,
        DateTime.UtcNow);
}

}

Running Tests

Run all integration tests

dotnet test tests/{name}.Api.IntegrationTests

Run with verbose output

dotnet test tests/{name}.Api.IntegrationTests -v n

Run specific test class

dotnet test tests/{name}.Api.IntegrationTests --filter "FullyQualifiedName~Create{Entity}Tests"

Run with code coverage

dotnet test tests/{name}.Api.IntegrationTests --collect:"XPlat Code Coverage"

Critical Rules

  • Fresh database per test - Use Respawn to reset

  • Isolated tests - No shared state between tests

  • Real database - Use Testcontainers, not in-memory

  • Test through HTTP - Use HttpClient, not direct calls

  • Authentication helpers - Easy user impersonation

  • Meaningful assertions - Verify both response and database

  • Test all status codes - 200, 400, 401, 403, 404, 409

  • Parallel execution - Design for concurrent tests

  • Clean up after tests - Respawn handles this

  • CI/CD compatible - Docker required for Testcontainers

Anti-Patterns to Avoid

// ❌ WRONG: Sharing state between tests private static Guid _sharedEntityId; // Tests will interfere!

// ✅ CORRECT: Create data per test [Fact] public async Task Test1() { var entityId = await CreateTestEntity(); }

// ❌ WRONG: Not resetting database // Tests depend on order and previous test data

// ✅ CORRECT: Reset before each test (handled by BaseIntegrationTest) public async Task InitializeAsync() { await _respawner.ResetAsync(connection); }

// ❌ WRONG: Using in-memory database services.AddDbContext<AppDbContext>(o => o.UseInMemoryDatabase("test")); // In-memory doesn't support all EF Core features

// ✅ CORRECT: Use real PostgreSQL with Testcontainers private readonly PostgreSqlContainer _dbContainer = new PostgreSqlBuilder() .WithImage("postgres:15-alpine") .Build();

Related Skills

  • unit-testing

  • Unit tests for handlers

  • jwt-authentication

  • Authentication to test

  • permission-authorization

  • Authorization to test

  • api-controller-generator

  • Endpoints to test

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

dotnet-clean-architecture

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

unit-testing

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

repository-pattern

No summary provided by upstream source.

Repository SourceNeeds Review