DEV Community

Cover image for .NET Aspire Integration Testing Best Practices for Distributed Applications
Anton Martyniuk
Anton Martyniuk

Posted on Originally published at antondevtips.com

.NET Aspire Integration Testing Best Practices for Distributed Applications

A while ago, I published an article about ASP.NET Core Integration Testing Best Practices.
WebApplicationFactory, TestContainers, and Respawn simplifies integration testing in .NET Core applications.

But .NET Aspire simplifies the process further for distributed applications.

.NET Aspire replaces both WebApplicationFactory and TestContainers with a single tool: DistributedApplicationTestingBuilder.
You no longer need to set up Docker containers manually or override environment variables.
Aspire handles service discovery, container orchestration, and connection strings for you.

In this post, I will share the best practices I discovered while writing integration tests for a distributed Aspire application with multiple APIs, PostgreSQL, and Redis.

In this post, we will explore:

  • The System We Will Be Testing
  • Best Practice 1: Use DistributedApplicationTestingBuilder Instead of WebApplicationFactory
  • Best Practice 2: Share the App Instance Across Tests with ICollectionFixture
  • Best Practice 3: Wait for Resources to Be Healthy
  • Best Practice 4: Wait for Services to Start before Running Tests
  • Best Practice 5: Cleanup Database Between Tests
  • Best Practice 6: Test your API Contracts
  • Best Practice 7: Test Error Responses and Cross-Service Communication

Let's dive in.


👉 Read original article on my newsletter: https://antondevtips.com/blog/dotnet-aspire-integration-testing-best-practices-for-distributed-applications

The System We Will Be Testing

I have built a distributed system with two APIs orchestrated by .NET Aspire:

  • Products API: manages products, supports CRUD operations and purchasing. Uses PostgreSQL and Redis for caching. Calls the Stocks API to check and update stock levels during purchases.
  • Stocks API: manages stock inventory for products. Uses PostgreSQL.

Both APIs share the same PostgreSQL server but use separate database schemas.

Here is the Aspire AppHost that defines the architecture:

var builder = DistributedApplication.CreateBuilder(args);

var postgres = builder.AddPostgres("postgres")
    .WithDataVolume(isReadOnly: false);

var redis = builder.AddRedis("cache");

var stocksApi = builder.AddProject<Projects.Stocks_Api>("stocks-api")
    .WithReference(postgres)
    .WaitFor(postgres)
    .WithExternalHttpEndpoints();

builder.AddProject<Projects.Products_Api>("products-api")
    .WithReference(stocksApi)
    .WaitFor(stocksApi)
    .WithReference(postgres)
    .WaitFor(postgres)
    .WithReference(redis)
    .WaitFor(redis)
    .WithExternalHttpEndpoints();

builder.Build().Run();
Enter fullscreen mode Exit fullscreen mode

The Products API depends on the Stocks API for inter-service communication.
When a user purchases a product, the Products API calls the Stocks API via HTTP to verify stock availability and update the count.

Both APIs use EF Core with PostgreSQL and run database migrations on startup.

Now let's explore how to write integration tests for this system.

Best Practice 1: Use DistributedApplicationTestingBuilder Instead of WebApplicationFactory

In my previous article, I used WebApplicationFactory together with TestContainers to spin up Docker containers for PostgreSQL and RabbitMQ.

With .NET Aspire, you don't need either of these.
Aspire provides DistributedApplicationTestingBuilder, which replaces both tools with a single, unified approach.

With the traditional approach, your test project references the API project directly:

<ProjectReference Include="..\Products.Api\Products.Api.csproj" />
Enter fullscreen mode Exit fullscreen mode

With Aspire, your test project references the Aspire AppHost project instead:

<ProjectReference Include="..\AspireNetConf.AppHost\AspireNetConf.AppHost.csproj" />
Enter fullscreen mode Exit fullscreen mode

This means you test the entire distributed application, not just a single API in isolation.

To create a test project, you can use the ready Aspire Test Project template:

You can install the Aspire project templates by running the following command:

dotnet new install Aspire.ProjectTemplates
Enter fullscreen mode Exit fullscreen mode

You will also need to install the Aspire CLI:

dotnet tool install --global aspire.cli
Enter fullscreen mode Exit fullscreen mode

The testing project installs the Aspire.Hosting.Testing NuGet package.

This one package replaces multiple packages from the traditional approach:

  • Microsoft.AspNetCore.Mvc.Testing (WebApplicationFactory)
  • Testcontainers.PostgreSql (TestContainers for PostgreSQL)
  • Testcontainers.RabbitMq (or other container packages)

To create the distributed application in your tests, use DistributedApplicationTestingBuilder:

var appHost = await DistributedApplicationTestingBuilder
    .CreateAsync<Projects.AspireTests_AppHost>();

var app = await appHost.BuildAsync();
await app.StartAsync();
Enter fullscreen mode Exit fullscreen mode

This starts the entire Aspire application, including all containers and services.

To create HTTP clients for your APIs, use CreateHttpClient with the resource name from your AppHost:

var productsClient = app.CreateHttpClient("products-api");
var stocksClient = app.CreateHttpClient("stocks-api");
Enter fullscreen mode Exit fullscreen mode

No need to configure base URLs or connection strings.
Aspire handles service discovery automatically.


👉 Read original article on my newsletter: https://antondevtips.com/blog/dotnet-aspire-integration-testing-best-practices-for-distributed-applications

Top comments (0)