.NET Core  

End-to-end integration testing with .NET Aspire

End-to-end (E2E) integration testing involves validating that all parts of your application—from front-end to back-end to external services—work together as expected.

In microservice or distributed .NET applications, setting up realistic test environments can be complex and error-prone.

.NET Aspire streamlines this by offering out-of-the-box service orchestration, environment management, and dependency discovery, which are key enablers for integration testing.

Integration testing

Key Benefits of Using Aspire for Integration Testing

Service Composition as Code

  • Aspire allows you to define and orchestrate your app's services (APIs, databases, message brokers, etc.) using fluent APIs in a central.AppHost or.AspireHost project.
  • This lets you spin up the exact configuration of your system as it would run in production, but scoped for testing.
    builder.AddProject<Projects.WebApi>("webapi");
    builder.AddContainer("postgres", "postgres:latest")
           .WithEnvironment("POSTGRES_PASSWORD", "test123");
    

Isolated, Reproducible Test Environments

  • Aspire creates containerized and isolated environments, so each test run can start from a clean state.
  • Great for avoiding side effects across tests.

Built-in Environment Configuration

You can inject environment variables, secrets, and connection strings into your services without manual config juggling.

.WithEnvironment("ASPNETCORE_ENVIRONMENT", "Testing");

Automatic Service Discovery

  • Aspire handles service-to-service communication with its discovery mechanism, so you don’t have to hardcode URLs or ports.
  • You can retrieve service endpoints programmatically in tests.

Mock and Real Service Swapping

Easily switch between real external services (like SQL, Redis, Kafka) and mock versions by swapping container images or using test doubles.

Observability & Troubleshooting

Use the Aspire Dashboard even during test runs to view logs, metrics, and dependencies, making it easier to debug failing tests.

Real-World Analogy

Think of Aspire as your test lab environment manager—it sets up your app, all its dependencies, injects test-specific settings, runs tests, and then cleans everything up.

Without Aspire, you'd manually configure Docker Compose, custom scripts, environment variables, service discovery, and more. Aspire replaces all that with a few lines of code.

Let’s see in Action: End-to-End Testing with .NET Aspire

I have a .NET Solution with Aspire App Host and Service default already that we created in previous chapters.

App Host

If you are new here, I would highly recommend checking out all the previous chapters for a better understanding.

  1. How to Get Started with .NET Aspire
  2. .NET Aspire: Setting Up Your Environment
  3. Core Concepts of .NET Aspire
  4. Enhancing Real-World Microservices with Resiliency in .NET Aspire
  5. Understanding Resiliency in .NET Aspire
  6. Service Orchestration in .NET Aspire
  7. Observability with the Aspire Dashboard
  8. .NET Aspire Service Discovery and Environment Configuration
  9. .NET Aspire integrations (SQL Server integration in Aspire Applications)

This is how my Aspire Dashboard looks with all the services running fine.

Aspire Dashboard

We may have many test case scenarios in a well-structured application with the following setup.

  • SQL container (SQL).
  • A database init service (database).
  • API (BlazorWeatherApp. API. csproj).
  • Blazor Web App (BlazorWeatherApp.WebApp.csproj).

Here in this chapter, we’ll try testing our API project with the following scenarios.

  1. API is up and running successfully.
  2. API returns correct data.
  3. API Performance, responding in less than 30 seconds.

Setting Up End-to-End Integration Testing in .NET Aspire with MSTest

.NET Aspire provides ready-to-use test templates to enable full end-to-end integration testing by simulating real microservice environments. Here's how you can configure and run these tests in your Aspire-based solution.

Step-by-Step Instructions

Step 1. Add a New Test Project.

  1. In Visual Studio, add a new project to your Aspire solution.
  2. Select “.NET Aspire Test Project (MSTest)” from the available templates.
  3. You may also choose other templates based on your preferred testing framework.
    • .NET Aspire Test Project (xUnit)
    • .NET Aspire Test Project (NUnit)
      Test project
  4. Name it appropriately (e.g., BlazorWeatherApp.Test).
    BlazorWeatherApp
  5. Once added, the new test project will include all required packages like.
    • Aspire.Hosting.Testing
    • MSTest
      Package

Step 2. Add a Project Reference to AppHost.

The .NET Aspire AppHost project acts as the orchestrator and contains references to all services, containers, and frontend projects. We must reference this AppHost project in our test project to enable proper resource resolution during test execution.

You can do this in one of three ways.

Option 1

  • Open Solution Explorer
  • Drag and drop the AppHost project onto the Test Project

Option 2

  • Right-click on the Test Project’s Dependencies
  • Select “Add Project Reference…” and choose the AppHost project
    Project Reference

Option 3 (Manual)

Edit the .csproj file of the Test Project.

<ItemGroup>
  <ProjectReference Include="..\BlazorWeatherApp.AppHost\BlazorWeatherApp.AppHost.csproj" />
</ItemGroup>

Edit

Step 3. Update Sample Test.

We are set to write the test case methods. We have one default file which you can use, or you can add a new file to write the test case.

  • The test project includes a default test file, IntegrationTest1.cs, with useful instructions.
     IntegrationTest
  • Open IntegrationTest1.cs and uncomment the first Test Method.
  • Replace the default project name in the “GetWebResourceRootReturnsOkStatusCode” method with your AppHost reference:
    AppHost reference

Step 4. Match Resource Names.

Till now, we have followed only the basic steps which is required for any test cases. Now, as per the name of this Test Method “GetWebResourceRootReturnsOkStatusCode” we need to check the “api” connection setting are right and returns 200 status while requesting from the client.

Question: What if we run the default test case?

Open the Test Explorer and hit the run button.

Test Explorer

It must fail as we have not changed the name for our service/API/endpoint name in the test method to create a right Http connection.

Endpoint

Ensure the right resource name used in the test (CreateHttpClient("webfrontend")) matches the name defined in your Program.cs in AppHost. For example.

Program.cs

Let’s change the same resource name in the test method and keep in mind to use the same every time in every test methods that you want to write for the same resource (api/frontend).

As I have changed “webfrontend” to “api” and passed the path “/health” in HttpClient.GetAsync().

Webfrontend

If you remember, this “/health" is an endpoint added by the Aspire Service default, which returns the service health status.

Step 5. Final Test Method Example.

Here’s a working sample for testing the api project.

API Project

Outcome

Your test project is now capable of executing realistic integration tests for Aspire-based APIs and services, ensuring health and response correctness using the actual distributed application runtime.

Adding a Test Case for Data Validation and API Behaviour

Let’s add one more test case to verify key aspects of our API response.

Test Objectives

  1. Ensure the data returned by the API is not null.
  2. Validate that the response contains the expected fields required by the UI.
  3. Check performance — the response is returned in less than 30 seconds.

Step 1. Define a Local DTO for Deserialization.

Add a local class WeatherForecast inside your test project to deserialize the response.

namespace BlazorWeatherApp.Test
{
    internal class WeatherForecast
    {
        public DateTime Date { get; set; }
        public int TemperatureC { get; set; }
        public string? Summary { get; set; }
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}

Note. This should match the structure returned by your “api” on the “/weatherforecast” endpoint.

Step 2. Add the Test Method.

Include the following [TestMethod] in your integration test class.

[TestMethod]
public async Task WeatherForecast_ShouldReturn_ValidData()
{
    // Arrange
    var appHost = await DistributedApplicationTestingBuilder.CreateAsync<Projects.BlazorWeatherApp_AppHost>();
    appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
    {
        clientBuilder.AddStandardResilienceHandler();
    });
    await using var app = await appHost.BuildAsync().WaitAsync(DefaultTimeout);
    await app.StartAsync().WaitAsync(DefaultTimeout);
    // Act
    var httpClient = app.CreateHttpClient("api");
    await app.ResourceNotifications.WaitForResourceHealthyAsync("api").WaitAsync(DefaultTimeout);
    var response = await httpClient.GetAsync("/weatherforecast");
    var content = await response.Content.ReadAsStringAsync();
    var data = JsonSerializer.Deserialize<List<WeatherForecast>>(content, new JsonSerializerOptions
    {
        PropertyNameCaseInsensitive = true
    });

    // Assert
    Assert.IsNotNull(data);             // Check if data is not null
    Assert.IsTrue(data.Count >= 5);     // Check if there are at least 5 items
}

What We Tested with This Method?
 

Test Aspect Description
API Connectivity Verified the API (/weatherforecast) is reachable and running.
Response Status Ensured the endpoint successfully responded to an HTTP GET request.
Response Content Deserialized and validated the structure of the returned data.
Data Integrity Confirmed that the data is not null and contains at least 5 entries.
Required Fields for UI Validated presence of essential properties: Date, TemperatureC, Summary.
API Responsiveness Ensured the response was returned within Aspire's default timeout (30s).

Step 3. Run and Validate.

Run and Validate

  • Use Test Explorer in Visual Studio to run all test cases.
  • Both tests should pass successfully, as shown in the screenshot:
    Visual Studio

What Happens Behind the Scenes in Aspire Integration Testing?

  1. AppHost is Instantiated: .CreateAsync<Projects.AppHost>() boots up the Aspire AppHost project in-memory to build the full microservice graph.
  2. Services are Launched: BuildAsync() and StartAsync() run your services (API, frontend, etc.) as real processes with dynamic port bindings.
  3. Health Checks Run: Aspire waits for services to become healthy using /health or custom endpoints before proceeding with tests.
  4. HttpClient is Connected: CreateHttpClient("api") maps to the running instance of your API service, allowing real HTTP calls.
  5. Test is Executed and Validated: Your test sends actual requests, validates responses, and checks assertions like status codes.
  6. Automatic Cleanup: After the test, Aspire shuts down all running services and cleans up resources.

Benefits of Using .NET Aspire Test Projects vs. Normal Unit Testing
 

Aspect Aspire Test Projects Traditional Unit Tests
Test Scope Full-stack: app + services + containers + infra Isolated: focuses on small, in-memory code units
Real Environment Simulation Runs your app with real databases, APIs, brokers, and config like in production Mocks or stubs everything—may miss real integration issues
Infrastructure Setup Done programmatically using Aspire.Hosting (AddProject, AddContainer, etc.) Manually mocked or ignored
Service Discovery Built-in with Aspire—no need to hardcode ports or URLs Must be manually injected or mocked
Config Management Use.WithEnvironment(), .With Parameters () to pass test-specific config Typically static or mocked
Observability Support Aspire Dashboard can be used during tests to debug services, logs, and traces No built-in dashboard; hard to diagnose integration failures
Test Real Behaviors Verifies real behavior across boundaries—e.g., SQL schema, service startup timing, failure handling Only verifies in-process logic
Reproducibility Aspire creates isolated and repeatable environments using containers and orchestrated services Depends heavily on test mocks and setup discipline
Easier CI Integration Aspire test environments can be spun up in CI pipelines (e.g., GitHub Actions, Azure Pipelines) Needs additional config for Docker, service dependencies

Important

Aspire-based test projects don’t replace unit tests—they complement them by enabling real-world testing in orchestrated environments.

This approach helps identify issues that unit tests and mocks can’t catch, such as configuration mismatches, dependency startup failures, or real-time behavior of services.