xUnitV3LoadFramework 2.0.0.63
See the version list below for details.
dotnet add package xUnitV3LoadFramework --version 2.0.0.63
NuGet\Install-Package xUnitV3LoadFramework -Version 2.0.0.63
<PackageReference Include="xUnitV3LoadFramework" Version="2.0.0.63" />
<PackageVersion Include="xUnitV3LoadFramework" Version="2.0.0.63" />
<PackageReference Include="xUnitV3LoadFramework" />
paket add xUnitV3LoadFramework --version 2.0.0.63
#r "nuget: xUnitV3LoadFramework, 2.0.0.63"
#:package xUnitV3LoadFramework@2.0.0.63
#addin nuget:?package=xUnitV3LoadFramework&version=2.0.0.63
#tool nuget:?package=xUnitV3LoadFramework&version=2.0.0.63
xUnitV3LoadFramework
xUnit v3-native load-style test runner for executing actions concurrently over a duration and reporting throughput/success/failure.
What It Is / What It Isn't
Best for:
- CI performance smoke tests
- Concurrency and regression checks
- Integration-style load tests inside
dotnet test - Quick validation that your API handles N concurrent requests
Not for:
- Distributed load generation across machines
- Protocol-specific clients (HTTP/2, gRPC, WebSocket)
- Full observability platform with dashboards
Install
dotnet add package xUnitV3LoadFramework
Why xUnit v3 Native?
This framework is a true xUnit v3 extension:
- Runs in
dotnet test— no extra tooling needed - Produces test failures visible in IDE and CI
- Supports filtering with
--filter - Respects xUnit cancellation
- Test method body becomes the action — no manual runner call needed
Quickstart
Native Attribute (Recommended)
The test method body runs automatically under load — no manual ExecuteAsync() call needed:
using xUnitV3LoadFramework.Attributes;
public class ApiLoadTests
{
private static readonly HttpClient _httpClient = new();
[Load(concurrency: 5, duration: 3000, interval: 500)]
public async Task Api_Should_Handle_Concurrent_Requests()
{
// This entire method body runs N times under load
var response = await _httpClient.GetAsync("https://api.example.com/health");
response.EnsureSuccessStatusCode();
}
}
Test passes if all iterations complete without exception. Test fails if any iteration throws or returns false.
Supported return types:
async Task— success if no exceptionvoid— success if no exceptionTask<bool>/ValueTask<bool>— success if returnstruebool— success if returnstrue
Fluent API
using xUnitV3LoadFramework.Extensions;
public class ApiLoadTests
{
private static readonly HttpClient _httpClient = new();
[Fact]
public async Task Api_Load_Test_Fluent()
{
var result = await LoadTestRunner.Create()
.WithName("HealthCheck_Load")
.WithConcurrency(10)
.WithDuration(TimeSpan.FromSeconds(5))
.WithInterval(TimeSpan.FromMilliseconds(200))
.RunAsync(async () =>
{
var response = await _httpClient.GetAsync("https://api.example.com/health");
response.EnsureSuccessStatusCode();
});
Assert.True(result.Success >= result.Total * 0.95);
}
}
Mixed Testing — All Attributes Work Together
[Load], [Fact], and [Theory] can coexist in the same test class:
public class ApiTests
{
private static readonly HttpClient _httpClient = new();
[Fact]
public void Should_Have_Valid_BaseUrl()
{
Assert.NotNull(_httpClient.BaseAddress);
}
[Theory]
[InlineData("/health")]
[InlineData("/ready")]
public async Task Endpoint_Should_Exist(string path)
{
var response = await _httpClient.GetAsync(path);
Assert.True(response.IsSuccessStatusCode);
}
[Load(concurrency: 5, duration: 3000, interval: 500)]
public async Task Api_Should_Handle_Load()
{
var response = await _httpClient.GetAsync("/health");
response.EnsureSuccessStatusCode();
}
}
Sample Output
Native [Load] test output:
Load Test Results:
Total: 30, Success: 28, Failure: 2
RPS: 9.8, Avg: 102ms, P95: 150ms, P99: 180ms
Result: FAILED (93.3% success rate)
Fluent API test output:
Load test 'HealthCheck_Load' completed:
Total executions: 50
Successful executions: 48
Failed executions: 2
Execution time: 5.12 seconds
Requests per second: 9.77
Average latency: 102.34ms
Success rate: 96.00%
Core Concepts
| Setting | Description |
|---|---|
| Concurrency | Number of concurrent operations launched per interval |
| Duration | Total time the load test runs |
| Interval | Time between launching batches of concurrent operations |
| TerminationMode | How the test stops: Duration (immediate), CompleteCurrentInterval (finish current batch), or StrictDuration (exact timing) |
| GracefulStopTimeout | Max time to wait for in-flight requests after duration expires. Default: 30% of duration, bounded 5-60s |
| Success | Action returns true or completes without exception |
| Failure | Action returns false or throws an exception |
| RequestsPerSecond | Total / Time — completed operations per second |
How Interval Works
Every Interval, the framework launches Concurrency concurrent operations. For example:
Concurrency: 5, Duration: 3s, Interval: 500ms= 6 batches × 5 operations = ~30 total operations
Advanced Configuration
Using LoadExecutionPlan Directly
For full control, use LoadExecutionPlan with LoadRunner.Run():
using LoadSurge.Models;
using LoadSurge.Runner;
[Fact]
public async Task Advanced_Load_Test()
{
var plan = new LoadExecutionPlan
{
Name = "Database_Connection_Pool",
Settings = new LoadSettings
{
Concurrency = 20,
Duration = TimeSpan.FromSeconds(30),
Interval = TimeSpan.FromMilliseconds(100),
TerminationMode = TerminationMode.CompleteCurrentInterval,
GracefulStopTimeout = TimeSpan.FromSeconds(10)
},
Action = async () =>
{
using var conn = new SqlConnection(connectionString);
await conn.OpenAsync();
return true;
}
};
var result = await LoadRunner.Run(plan);
Assert.True(result.Success >= result.Total * 0.99);
}
Termination Modes
| Mode | Behavior |
|---|---|
Duration |
Stops immediately when duration expires (default) |
CompleteCurrentInterval |
Waits for current batch to finish before stopping |
StrictDuration |
Strict timing — may cut off final batch |
MaxIterations (Fluent API)
Stop after a fixed number of operations regardless of duration:
var result = await LoadTestRunner.Create()
.WithConcurrency(10)
.WithDuration(TimeSpan.FromMinutes(5))
.WithMaxIterations(1000) // Stop after 1000 operations
.RunAsync(async () => { /* ... */ });
Assertions / CI Gating
Use LoadResult fields to fail tests based on performance criteria:
var result = await LoadTestRunner.Create()
.WithConcurrency(10)
.WithDuration(TimeSpan.FromSeconds(10))
.RunAsync(async () => { /* ... */ });
// Success rate gate
var successRate = (double)result.Success / result.Total;
Assert.True(successRate >= 0.99, $"Success rate {successRate:P} below 99%");
// Throughput gate
Assert.True(result.RequestsPerSecond >= 50, $"RPS {result.RequestsPerSecond} below 50");
// Latency gate
Assert.True(result.Percentile95Latency < 500, $"P95 latency {result.Percentile95Latency}ms exceeds 500ms");
Assert.True(result.AverageLatency < 200, $"Avg latency {result.AverageLatency}ms exceeds 200ms");
Available Result Fields
Total,Success,Failure— countsTime— execution time in secondsRequestsPerSecond— throughputAverageLatency,MinLatency,MaxLatency— in millisecondsMedianLatency,Percentile95Latency,Percentile99Latency— percentiles in msPeakMemoryUsage— bytes
How Failures Work
The framework runs all iterations to completion before determining pass/fail:
- All iterations execute regardless of individual failures
- Exceptions are caught and counted as failures (not thrown)
- At the end, a report shows Total/Success/Failure counts
- Test is marked FAILED if
Failure > 0
This means you always get complete metrics, even when some iterations fail.
Native [Load] tests: Pass/fail is automatic based on iteration results.
Fluent API tests: You control pass/fail with assertions:
var result = await LoadTestRunner.Create()
.WithConcurrency(10)
.WithDuration(TimeSpan.FromSeconds(5))
.RunAsync(async () => { /* ... */ });
// Allow up to 5% failure rate
var successRate = (double)result.Success / result.Total;
Assert.True(successRate >= 0.95, $"Success rate {successRate:P} below 95%");
Safety & Gotchas
- Thread-safety: Your action runs concurrently. Avoid shared mutable state unless protected.
- Reuse HttpClient: Create a single
static readonly HttpClient— don't instantiate per request. - Start low: Begin with low concurrency and short duration. Increase gradually.
- Timeouts: Add your own timeout in the action. The framework won't kill hung operations:
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); await httpClient.GetAsync(url, cts.Token); - Cancellation: The framework uses
GracefulStopTimeoutto wait for in-flight requests. Long-running actions without cancellation support may delay test completion. - CI filtering: Use
dotnet test --filter "FullyQualifiedName~LoadTests"to run only load tests or exclude them from fast CI runs.
When to Choose This
Use this framework when:
- You want load tests as part of
dotnet testwithout extra tooling - You need quick concurrency smoke tests in CI
- Your tests are integration-style (HTTP calls, database queries)
- You want xUnit v3 native attributes and test discovery
Use a dedicated tool when:
- You need distributed load from multiple machines
- You need protocol-specific features (HTTP/2 multiplexing, WebSocket)
- You need real-time dashboards and detailed analytics
Fun Explanation (optional)
Think of this like a playground stress test. You set:
- How many kids play at once (
Concurrency) - How long the playground is open (
Duration) - How often new groups arrive (
Interval)
The framework tells you how many kids had fun (success), how many fell off the swings (failure), and how fast the line moved (RPS).
Requirements
- .NET 8.0+ or .NET Framework 4.7.2+ (netstandard2.0)
- xUnit v3
Contributing
PRs welcome. Open an issue for bugs or feature requests.
Made by Vasyl
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- LoadSurge (>= 1.0.0.49)
- Microsoft.Bcl.AsyncInterfaces (>= 10.0.1)
- Microsoft.SourceLink.GitHub (>= 8.0.0)
- xunit.v3.extensibility.core (>= 3.2.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.0.66 | 83 | 12/15/2025 |
| 2.0.0.65 | 79 | 12/15/2025 |
| 2.0.0.64 | 82 | 12/15/2025 |
| 2.0.0.63 | 82 | 12/15/2025 |
| 2.0.0.62 | 75 | 12/15/2025 |
| 2.0.0.61 | 80 | 12/15/2025 |
| 2.0.0.60 | 83 | 12/15/2025 |
| 2.0.0.58 | 76 | 12/15/2025 |
| 2.0.0.57 | 101 | 12/13/2025 |
| 2.0.0.56 | 97 | 12/13/2025 |
| 2.0.0.55 | 186 | 12/5/2025 |
| 2.0.0.54 | 177 | 12/4/2025 |
| 2.0.0.53 | 649 | 12/1/2025 |
| 2.0.0.52 | 556 | 12/1/2025 |
| 2.0.0.51 | 276 | 11/16/2025 |
| 2.0.0.50 | 271 | 11/16/2025 |
| 2.0.0.49 | 270 | 11/16/2025 |
| 2.0.0.48 | 219 | 11/16/2025 |
| 2.0.0.47 | 207 | 11/14/2025 |
| 2.0.0.46 | 198 | 11/14/2025 |
| 2.0.0.45 | 201 | 11/14/2025 |
| 2.0.0.44 | 204 | 11/14/2025 |
| 2.0.0.43 | 204 | 11/14/2025 |
| 2.0.0.42 | 208 | 11/14/2025 |
| 2.0.0.41 | 179 | 10/22/2025 |
| 2.0.0.38 | 166 | 10/21/2025 |
| 2.0.0.36 | 178 | 9/12/2025 |
| 2.0.0.35 | 182 | 9/12/2025 |
| 2.0.0.27 | 389 | 8/5/2025 |
| 2.0.0.22 | 144 | 8/3/2025 |
| 2.0.0.21 | 150 | 8/3/2025 |
| 2.0.0.20 | 152 | 8/3/2025 |
| 2.0.0.19 | 148 | 8/3/2025 |
| 1.0.0.95 | 67 | 8/2/2025 |
| 1.0.0.94 | 60 | 8/1/2025 |
| 1.0.0.93 | 61 | 8/1/2025 |
| 1.0.0.92 | 139 | 7/29/2025 |
| 1.0.0.91 | 147 | 7/28/2025 |
| 1.0.0.90 | 144 | 7/28/2025 |
| 1.0.0.87 | 372 | 7/25/2025 |
| 1.0.0.86 | 370 | 7/25/2025 |
| 1.0.0.85 | 500 | 7/24/2025 |
| 1.0.0.84 | 491 | 7/24/2025 |
| 1.0.0.83 | 545 | 7/23/2025 |
| 1.0.0.81 | 550 | 7/22/2025 |
| 1.0.0.80 | 542 | 7/22/2025 |
| 1.0.0.79 | 547 | 7/22/2025 |
| 1.0.0.78 | 546 | 7/22/2025 |
| 1.0.0.77 | 552 | 7/22/2025 |
| 1.0.0.76 | 180 | 7/7/2025 |
| 1.0.0.75 | 169 | 7/7/2025 |
| 1.0.0.74 | 103 | 7/5/2025 |
| 1.0.0.73 | 277 | 5/12/2025 |
| 1.0.0.72 | 233 | 5/11/2025 |
| 1.0.0.71 | 219 | 5/11/2025 |
| 1.0.0.70 | 126 | 5/9/2025 |
| 1.0.0.69 | 123 | 5/9/2025 |
| 1.0.0.68 | 121 | 5/9/2025 |
| 1.0.0.67 | 130 | 5/9/2025 |
| 1.0.0.66 | 132 | 5/9/2025 |
| 1.0.0.65 | 133 | 5/9/2025 |
| 1.0.0.64 | 167 | 5/9/2025 |
| 1.0.0.63 | 179 | 5/9/2025 |
| 1.0.0.62 | 187 | 4/30/2025 |
| 1.0.0.61 | 183 | 4/30/2025 |
| 1.0.0.60 | 182 | 4/30/2025 |
| 1.0.0.57 | 180 | 4/30/2025 |
Version 2.0.0: Full xUnit v3 compatibility, fluent API, enhanced performance metrics, and production-ready features. See CHANGELOG.md for complete details.