Skip to main content Inicio Creadores rudironsoni synaxis dotnet-testing-advanced-webapi-integration-testing
dotnet-testing-advanced-webapi-integration-testing Complete guide for ASP.NET Core Web API integration testing. Use when performing integration testing on Web API endpoints or validating ProblemDetails error format. Covers WebApplicationFactory, IExceptionHandler, Testcontainers multi-container orchestration, Flurl URL construction, and AwesomeAssertions HTTP validation.
Keywords: webapi integration testing, WebApplicationFactory, asp.net core integration test, webapi integration test, IExceptionHandler, ProblemDetails, ValidationProblemDetails, AwesomeAssertions, Flurl, Respawn, Be201Created, Be400BadRequest, multi-container testing, Collection Fixture, global exception handling
Ir a la instalación Skills Marketplace Descubre y explora habilidades de IA creadas por la comunidad.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Copiar promptMostrar detalles del prompt Un comando directo omite el prompt de revisión. Revisa el origen antes de ejecutarlo.
npx skills add https://github.com/rudironsoni/Synaxis --skill dotnet-testing-advanced-webapi-integration-testingEl comando permanece en una sola línea. Desplázate horizontalmente para revisarlo antes de copiarlo.
¿Prefieres una copia local? Descarga los archivos que SkillsMP tiene disponibles ahora.
Descargar Zip Descargando... Ocupaciones relacionadas SOC
Basado en la clasificación ocupacional SOC
name dotnet-testing-advanced-webapi-integration-testing category testing subcategory integration description Complete guide for ASP.NET Core Web API integration testing. Use when performing integration testing on Web API endpoints or validating ProblemDetails error format. Covers WebApplicationFactory, IExceptionHandler, Testcontainers multi-container orchestration, Flurl URL construction, and AwesomeAssertions HTTP validation.
Keywords: webapi integration testing, WebApplicationFactory, asp.net core integration test, webapi integration test, IExceptionHandler, ProblemDetails, ValidationProblemDetails, AwesomeAssertions, Flurl, Respawn, Be201Created, Be400BadRequest, multi-container testing, Collection Fixture, global exception handling
targets ["*"] license MIT metadata {"author":"Kevin Tseng","version":"1.0.0","tags":"webapi, integration-testing, testcontainers, aspnetcore, clean-architecture","related_skills":"advanced-aspnet-integration-testing, advanced-testcontainers-database, advanced-aspire-testing"} claudecode {} opencode {} codexcli {"short-description":".NET skill guidance for dotnet-testing-advanced-webapi-integration-testing"} copilot {} geminicli {} antigravity {}
Source: kevintsengtw/dotnet-testing-agent-skills (MIT). Ported into dotnet-agent-harness.
Web API Integration Testing
Applicable Scenarios
Skill Level : Advanced
Prerequisites : xUnit basics, ASP.NET Core basics, Testcontainers basics, Clean Architecture
Estimated Learning Time : 60-90 minutes
Learning Objectives
After completing this skill, you will be able to:
Establish complete Web API integration testing architecture
Implement modern exception handling using IExceptionHandler
Validate standard ProblemDetails and ValidationProblemDetails format
Use Flurl to simplify URL construction for HTTP testing
Use AwesomeAssertions for precise HTTP response validation
Establish multi-container (PostgreSQL + Redis) testing environment
Core Concepts
IExceptionHandler - Modern Exception Handling
The IExceptionHandler interface introduced in ASP.NET Core 8+ provides a more elegant error handling approach than
traditional middleware:
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger<GlobalExceptionHandler> _logger;
public GlobalExceptionHandler (ILogger<GlobalExceptionHandler> logger )
{
_logger = logger;
}
public async ValueTask<bool > TryHandleAsync (
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken )
{
_logger.LogError(exception, "Unhandled exception occurred: {Message}" , exception.Message);
problemDetails = CreateProblemDetails(exception);
httpContext.Response.StatusCode = problemDetails.Status ?? ;
httpContext.Response.ContentType = ;
httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken);
;
}
{
exception
{
KeyNotFoundException => ProblemDetails
{
Type = ,
Title = ,
Status = ,
Detail = exception.Message
},
ArgumentException => ProblemDetails
{
Type = ,
Title = ,
Status = ,
Detail = exception.Message
},
_ => ProblemDetails
{
Type = ,
Title = ,
Status = ,
Detail =
}
};
}
}
```text
RFC defined unified error response format:
| Field | Description |
| ----- | ----------- |
| `type` | URI problem type |
| `title` | Short error description |
| `status` | HTTP status code |
| `detail` | Detailed error explanation |
| `instance` | URI of problem occurrence |
```json
{
: ,
: ,
: ,
: ,
: {
: [ ],
: [ ]
}
}
```text
FluentValidation exception handler implements the `IExceptionHandler` , ` ` ` ` . , ( ) .
> 📖 [ / - - ]( / - - )
##
###
```
: < >,
{
PostgreSqlContainer? _postgresContainer;
RedisContainer? _redisContainer;
FakeTimeProvider? _timeProvider;
PostgreSqlContainer PostgresContainer => _postgresContainer
?? InvalidOperationException( );
RedisContainer RedisContainer => _redisContainer
?? InvalidOperationException( );
FakeTimeProvider TimeProvider => _timeProvider
?? InvalidOperationException( );
{
_postgresContainer = PostgreSqlBuilder()
.WithImage( )
.WithDatabase( )
.WithUsername( )
.WithPassword( )
.WithCleanUp( )
.Build();
_redisContainer = RedisBuilder()
.WithImage( )
.WithCleanUp( )
.Build();
_timeProvider = FakeTimeProvider( DateTimeOffset( , , , , , , TimeSpan.Zero));
_postgresContainer.StartAsync();
_redisContainer.StartAsync();
}
{
builder.ConfigureAppConfiguration(config =>
{
config.Sources.Clear();
config.AddInMemoryCollection( Dictionary< , ?>
{
[ ] = PostgresContainer.GetConnectionString(),
[ ] = RedisContainer.GetConnectionString(),
[ ] =
});
});
builder.ConfigureServices(services =>
{
services.Remove(services.Single(d => d.ServiceType == (TimeProvider)));
services.AddSingleton<TimeProvider>(TimeProvider);
});
builder.UseEnvironment( );
}
{
(_postgresContainer != ) _postgresContainer.DisposeAsync();
(_redisContainer != ) _redisContainer.DisposeAsync();
.DisposeAsync();
}
}
```text
```csharp
[ ]
: < >
{
Name = ;
}
```text
```csharp
[ ]
:
{
TestWebApplicationFactory Factory;
HttpClient HttpClient;
DatabaseManager DatabaseManager;
IFlurlClient FlurlClient;
{
Factory = factory;
HttpClient = factory.CreateClient();
DatabaseManager = DatabaseManager(factory.PostgresContainer.GetConnectionString());
FlurlClient = FlurlClient(HttpClient);
}
{
DatabaseManager.InitializeDatabaseAsync();
}
{
DatabaseManager.CleanDatabaseAsync();
FlurlClient.Dispose();
}
{
Factory.TimeProvider.SetUtcNow( DateTimeOffset( , , , , , , TimeSpan.Zero));
}
{
Factory.TimeProvider.Advance(timeSpan);
}
}
```text
Flurl provides fluent API building complex URLs:
```csharp
url = ;
url =
.SetQueryParam( , )
.SetQueryParam( , )
.SetQueryParam( , );
```text
```csharp
[ ]
{
request = ProductCreateRequest { Name = , Price = m };
response = HttpClient.PostAsJsonAsync( , request);
response.Should().Be201Created()
.And.Satisfy<ProductResponse>(product =>
{
product.Id.Should().NotBeEmpty();
product.Name.Should().Be( );
product.Price.Should().Be( m);
});
}
```text
```csharp
[ ]
{
invalidRequest = ProductCreateRequest { Name = , Price = m };
response = HttpClient.PostAsJsonAsync( , invalidRequest);
response.Should().Be400BadRequest()
.And.Satisfy<ValidationProblemDetails>(problem =>
{
problem.Type.Should().Be( );
problem.Title.Should().Be( );
problem.Errors.Should().ContainKey( );
problem.Errors[ ].Should().Contain( );
});
}
```text
```csharp
[ ]
{
nonExistentId = Guid.NewGuid();
response = HttpClient.GetAsync( );
response.Should().Be404NotFound()
.And.Satisfy<ProblemDetails>(problem =>
{
problem.Type.Should().Be( );
problem.Title.Should().Be( );
problem.Status.Should().Be( );
});
}
```text
```csharp
[ ]
{
TestHelpers.SeedProductsAsync(DatabaseManager, );
url =
.SetQueryParam( , )
.SetQueryParam( , );
response = HttpClient.GetAsync(url);
response.Should().Be200Ok()
.And.Satisfy<PagedResult<ProductResponse>>(result =>
{
result.Total.Should().Be( );
result.PageSize.Should().Be( );
result.Page.Should().Be( );
result.Items.Should().HaveCount( );
});
}
```text
```csharp
{
{
ProductCreateRequest { Name = name, Price = price };
}
{
tasks = Enumerable.Range( , count)
.Select(i => SeedSpecificProductAsync(dbManager, , i * m));
Task.WhenAll(tasks);
}
}
```text
```text
tests/Integration/
└── SqlScripts/
└── Tables/
└── CreateProductsTable.sql
```text
- **Single Responsibility**: Each test focuses one specific scenario
- ** A Pattern**: Clear separation of Arrange, Act, Assert
- **Clear Naming**: Method name expresses test intent
- **ValidationProblemDetails**: Validate error response format
- **ProblemDetails**: Validate business exception response
- **HTTP Status Code**: Confirm correct status code
- **Container Sharing**: Use Collection Fixture
- **Data Cleanup**: Clean data after tests, don s Testing Practice - Day Challengevar
500
"application/problem+json"
await
return
true
private static ProblemDetails CreateProblemDetails (Exception exception )
return
switch
new
"https://httpstatuses.com/404"
"Resource Not Found"
404
new
"https://httpstatuses.com/400"
"Invalid Parameters"
400
new
"https://httpstatuses.com/500"
"Internal Server Error"
500
"An unexpected error occurred"
### ProblemDetails Standard Format
7807
for
### ValidationProblemDetails - Validation Error Specific
"type"
"https://tools.ietf.org/html/rfc9110#section-15.5.1"
"title"
"One or more validation errors occurred."
"status"
400
"detail"
"Input data contains validation errors"
"errors"
"Name"
"Product name cannot be empty"
"Price"
"Product price must be greater than 0"
### FluentValidation Exception Handler
interface
specifically
handling
ValidationException
and
converting
validation
errors
to
standard
ValidationProblemDetails
format
response
Handlers
execute
in
registration
order
and
specific
handlers
like
FluentValidation
must
be
registered
before
global
handlers
Complete
implementation
code
please
refer
to
references
exception
handler
details.md
references
exception
handler
details.md
Integration
Testing
Infrastructure
TestWebApplicationFactory
csharp
public
class
TestWebApplicationFactory
WebApplicationFactory
Program
IAsyncLifetime
private
private
private
public
throw
new
"PostgreSQL container has not been initialized"
public
throw
new
"Redis container has not been initialized"
public
throw
new
"TimeProvider has not been initialized"
public async Task InitializeAsync ()
new
"postgres:16-alpine"
"test_db"
"testuser"
"testpass"
true
new
"redis:7-alpine"
true
new
new
2024
1
1
0
0
0
await
await
protected override void ConfigureWebHost (IWebHostBuilder builder )
new
string
string
"ConnectionStrings:DefaultConnection"
"ConnectionStrings:Redis"
"Logging:LogLevel:Default"
"Warning"
typeof
"Testing"
public new async Task DisposeAsync ()
if
null
await
if
null
await
await
base
### Collection Fixture Pattern
CollectionDefinition("Integration Tests" )
public
class
IntegrationTestCollection
ICollectionFixture
TestWebApplicationFactory
public
const
string
"Integration Tests"
### Test Base Class
Collection("Integration Tests" )
public
abstract
class
IntegrationTestBase
IAsyncLifetime
protected
readonly
protected
readonly
protected
readonly
protected
readonly
protected IntegrationTestBase (TestWebApplicationFactory factory )
new
new
public virtual async Task InitializeAsync ()
await
public virtual async Task DisposeAsync ()
await
protected void ResetTime ()
new
2024
1
1
0
0
0
protected void AdvanceTime (TimeSpan timeSpan )
## Flurl Simplifies URL Construction
for
var
$"/products?pageSize={pageSize} &page={page} &keyword={keyword} "
var
"/products"
"pageSize"
5
"page"
2
"keyword"
"special"
## Testing Examples
### Successful Product Creation Test
Fact
public async Task CreateProduct_WithValidData_ShouldCreateProductSuccessfully ()
var
new
"New Product"
299.99
var
await
"/products"
"New Product"
299.99
### Validation Error Test
Fact
public async Task CreateProduct_WhenProductNameIsEmpty_ShouldReturn400BadRequest ()
var
new
""
100.00
var
await
"/products"
"https://tools.ietf.org/html/rfc9110#section-15.5.1"
"One or more validation errors occurred."
"Name"
"Name"
"Product name cannot be empty"
### Resource Not Found Test
Fact
public async Task GetById_WhenProductDoesNotExist_ShouldReturn404WithProblemDetails ()
var
var
await
$"/Products/{nonExistentId} "
"https://httpstatuses.com/404"
"Product does not exist"
404
### Pagination Query Test
Fact
public async Task GetProducts_WithPaginationParameters_ShouldReturnCorrectPagedResult ()
await
15
var
"/products"
"pageSize"
5
"page"
2
var
await
15
5
2
5
## Data Management Strategy
### TestHelpers Design
public
static
class
TestHelpers
public static ProductCreateRequest CreateProductRequest (
string name = "Test Product" ,
decimal price = 100.00 m )
return
new
public static async Task SeedProductsAsync (DatabaseManager dbManager, int count )
var
1
$"Product {i:D2} "
10.0
await
### SQL Script Externalization
## Best Practices
### 1. Test Structure Design
on
3
### 2. Error Handling Validation
### 3. Performance Considerations
't recreate containers
- **Parallel Execution**: Ensure test independence
## Dependency Packages
```xml
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="AwesomeAssertions" Version="9.1.0" />
<PackageReference Include="Testcontainers.PostgreSql" Version="4.0.0" />
<PackageReference Include="Testcontainers.Redis" Version="4.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
<PackageReference Include="Flurl" Version="4.0.0" />
<PackageReference Include="Respawn" Version="6.2.1" />
```text
## Project Structure
```text
src/
├── Api/ # Web API layer
├── Application/ # Application service layer
├── Domain/ # Domain model
└── Infrastructure/ # Infrastructure layer
tests/
└── Integration/
├── Fixtures/
│ ├── TestWebApplicationFactory.cs
│ ├── IntegrationTestCollection.cs
│ └── IntegrationTestBase.cs
├── Handlers/
│ ├── GlobalExceptionHandler.cs
│ └── FluentValidationExceptionHandler.cs
├── Helpers/
│ ├── DatabaseManager.cs
│ └── TestHelpers.cs
├── SqlScripts/
│ └── Tables/
└── Controllers/
└── ProductsControllerTests.cs
```text
## Reference Resources
### Original Articles
This skill content is distilled from the "Old School Software Engineer'
30
" article series:
- **Day 23 - Integration Testing in Practice: Web API Service Integration Testing**
- Article: https://ithelp.ithome.com.tw/articles/10376873
- Sample code: https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day23
### Official Documentation
- [ASP.NET Core Integration Testing](https://docs.microsoft.com/aspnet/core/test/integration-tests)
- [IExceptionHandler Documentation](https://learn.microsoft.com/aspnet/core/fundamentals/error-handling)
- [ProblemDetails RFC 7807](https://tools.ietf.org/html/rfc7807)
- [Testcontainers for .NET](https://dotnet.testcontainers.org/)
- [AwesomeAssertions](https://awesomeassertions.org/)
- [Flurl HTTP Client](https://flurl.dev/)
- [Respawn](https://github.com/jbogard/Respawn)