K6 + .NET Aspire: Seamless Load Testing



This content originally appeared on DEV Community and was authored by Nhan Nguyen

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:

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

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

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 }),
  };
}

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


This content originally appeared on DEV Community and was authored by Nhan Nguyen