Introduction
As modern applications become increasingly distributed, performance testing becomes both more critical and more complex. Traditional approaches often involve setting up separate environments, managing extra tools, and fighting configuration bloat.
But what if load testing could be built directly into your orchestration layer?
In this post, Iโll show you how to integrate K6 load testing seamlessly into a .NET Aspire application. The result is a zero-friction setup where performance tests run alongside your microservices with just:
dotnet aspire run
The Challenge: Testing Distributed Systems
While building the BookWorm platformโan e-commerce app composed of multiple services like Catalog
, Basket
, Ordering
, and Chat
โ I faced these typical challenges:
- ๐ Environment Complexity: Multiple services, databases, and message brokers
- โ๏ธ Configuration Overhead: Load testing tools require their own pipelines and configs
- ๐จโ๐ป Developer Friction: Setting up test scenarios slows down iteration
- ๐ Real-World Simulation: Tests must mimic true user behavior across services
Enter .NET Aspire + K6: A Perfect Match
.NET Aspire provides service orchestration, while K6 offers modern, scriptable load testing.
This combo brings:
- โ Zero Setup: Run tests with dotnet aspire run
- โ Service Discovery: Automatically wires service endpoints
- โ TypeScript Authoring: Full IDE support and intellisense
- โ Integrated Dashboards: K6 dashboard lives inside Aspire UI
- โ Realistic Scenarios: Simulates complex user flows across services
Build Process Overview
The integration seamlessly handles the TypeScript-to-JavaScript build pipeline during the Aspire build process:
Step-by-Step Integration
1. ๐ฆ Install the K6 Hosting Package
Install the Aspire-compatible K6 extension:
dotnet add package CommunityToolkit.Aspire.Hosting.k6
Or add to your .csproj
:
<PackageReference Include="CommunityToolkit.Aspire.Hosting.k6" Version="*" />
2. ๐งฑ MSBuild Integration for TypeScript Compilation
Create a K6Integration.targets
file in your AppHost project:
<Project>
<Target Name="BuildK6Container" BeforeTargets="Build">
<Message Text="Installing node packages" Importance="high" />
<Exec Command="npm install" WorkingDirectory="Container/k6" Condition="Exists('Container/k6/package.json')" />
<Message Text="Formatting K6 code" Importance="high" />
<Exec Command="npm run format:check" WorkingDirectory="Container/k6" Condition="Exists('Container/k6/package.json')" ContinueOnError="true" />
<Message Text="Bundle k6 scripts" Importance="high" />
<Exec Command="npm run build" WorkingDirectory="Container/k6" Condition="Exists('Container/k6/package.json')" />
</Target>
</Project>
Then import it in your AppHost .csproj
:
<Import Project="K6Integration.targets" />
3. ๐งฉ Aspire Extension for K6
public static void AddK6(
this IDistributedApplicationBuilder builder,
IResourceBuilder<YarpResource> entryPoint,
int vus = 10)
{
builder
.AddK6("k6")
.WithImagePullPolicy(ImagePullPolicy.Always)
.WithBindMount("Container/k6", "/scripts", true)
.WithBindMount("Container/k6/dist", "/home/k6")
.WithScript("/scripts/dist/main.js", vus)
.WithReference(entryPoint.Resource.GetEndpoint(Protocol.Http))
.WithEnvironment("K6_WEB_DASHBOARD", "true")
.WithEnvironment("K6_WEB_DASHBOARD_EXPORT", "dashboard-report.html")
.WithHttpEndpoint(targetPort: 5665, name: "k6-dashboard")
.WithUrlForEndpoint("k6-dashboard", url => url.DisplayText = "K6 Dashboard")
.WithK6OtlpEnvironment()
.WaitFor(entryPoint);
}
4. ๐งฉ Integrate K6 into Your AppHost
var builder = DistributedApplication.CreateBuilder(args);
// ... configure your services (databases, APIs, etc.)
var gateway = builder
.AddYarp("gateway")
.LoadFromConfiguration("ReverseProxy")
.WithReference("catalog-api")
.WithReference("basket-api")
// ... other service references
// Add K6 only in run mode
if (builder.ExecutionContext.IsRunMode)
{
builder.AddK6(gateway); // ๐ฏ That's it!
}
builder.Build().Run();
5. ๐งโ๐ป Authoring Scenarios in TypeScript
The real power comes from structured, TypeScript-based test scenarios. Here's the project structure:
Container/k6/
โโโ src/
โ โโโ main.ts
โ โโโ scenarios/
โ โ โโโ browse-catalog.ts
โ โ โโโ search-filter.ts
โ โ โโโ api-comprehensive.ts
โ โโโ utils/
โ โโโ types/
โโโ package.json
โโโ tsconfig.json
โโโ webpack.config.js
๐ก You can view the full working K6 test code in the BookWorm repository
๐ BookWorm K6 Source Code
Example:
group("Browse Catalog", () => {
let res = http.get(`${BASE_URL}/`);
check(res, { "homepage loaded": (r) => r.status === 200 });
const books = JSON.parse(res.body);
if (books.length) {
const bookId = books[0].id;
res = http.get(`${BASE_URL}/catalog/api/v1/books/${bookId}`);
}
sleep(1 + Math.random() * 2);
});
6. โ๏ธ Smart Test Configuration
export const options = {
scenarios: {
browse_catalog: {
executor: "ramping-vus",
stages: [
{ duration: "30s", target: 3 },
{ duration: "1m", target: 5 },
{ duration: "30s", target: 0 },
],
env: { scenario: "browse_catalog" },
},
},
thresholds: {
http_req_duration: ["p(95)<1000"],
http_req_failed: ["rate<0.1"],
},
};
7. ๐ฅ Simulating Real User Journeys
export function browseCatalogScenario() {
const res = http.get(`${BASE_URL}/`);
sleep(random(1, 3));
}
Developer Experience
dotnet aspire run
This command:
- ๐งฑ Starts all services
- ๐งช Runs K6 scenarios
- ๐ Launches Aspire dashboard
- ๐ Opens integrated K6 UI
Observability and Reporting
๐ด Real-Time Dashboard
Live insights into:
- Requests per second
- P95 response times
- Active users per scenario
- Failures and retries
๐งพ Test Summary Reports
export function handleSummary(data) {
return {
"summary.html": htmlReport(data),
"summary.json": JSON.stringify(data, null, 2),
stdout: textSummary(data, { indent: " ", enableColors: true }),
};
}
Example output:
Benefits and Results
For Developers
- โ Zero-setup performance tests
- โ Full TypeScript IDE support
- โ Instant feedback from local testing
For Teams
- ๐งช Shift-left testing in dev environments
- ๐ Reproducible results with seeded randomness
- ๐ฌ Realistic test coverage across services
Real-World Results
- ๐ซ Caught a 300ms latency bug in Catalog search
- ๐งฎ Optimized DB queries reducing P95 by 40%
- ๐ฆ Verified gateway can handle 50+ concurrent users
- ๐ Detected a memory leak in Basket service under load
Conclusion
With .NET Aspire and K6 working together, performance testing becomes just another part of your development loopโnot an afterthought.
You can:
- Catch performance regressions early
- Simulate real user flows
- Simplify your testing infrastructure
- Empower every dev to think about performance
Top comments (0)