DEV Community

Cover image for K6 + .NET Aspire: Seamless Load Testing
Nhan Nguyen
Nhan Nguyen

Posted on

K6 + .NET Aspire: Seamless Load Testing

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
Enter fullscreen mode Exit fullscreen mode

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:

K6 Build Processs

Step-by-Step Integration

1. ๐Ÿ“ฆ Install the K6 Hosting Package

Install the Aspire-compatible K6 extension:

dotnet add package CommunityToolkit.Aspire.Hosting.k6
Enter fullscreen mode Exit fullscreen mode

Or add to your .csproj:

<PackageReference Include="CommunityToolkit.Aspire.Hosting.k6" Version="*" />
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Then import it in your AppHost .csproj:

<Import Project="K6Integration.targets" />
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

๐Ÿ’ก 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);
});

Enter fullscreen mode Exit fullscreen mode

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"],
  },
};
Enter fullscreen mode Exit fullscreen mode

7. ๐Ÿ‘ฅ Simulating Real User Journeys

export function browseCatalogScenario() {
  const res = http.get(`${BASE_URL}/`);
  sleep(random(1, 3));
}
Enter fullscreen mode Exit fullscreen mode

Developer Experience

dotnet aspire run
Enter fullscreen mode Exit fullscreen mode

This command:

  • ๐Ÿงฑ Starts all services
  • ๐Ÿงช Runs K6 scenarios
  • ๐Ÿ“Š Launches Aspire dashboard
  • ๐Ÿ” Opens integrated K6 UI

Observability and Reporting

๐Ÿ”ด Real-Time Dashboard

K6 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 }),
  };
}
Enter fullscreen mode Exit fullscreen mode

Example output:

K6 Reporter

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

Resources

Top comments (0)