Перейти к содержимому

Write your first test

Это содержимое пока не доступно на вашем языке.

Select your testing framework

In this article, you’ll learn how to create a test project, write C# tests, and run them for your Aspire solutions. These aren’t unit tests—they’re functional and integration tests that validate how your distributed application components work together. Aspire provides testing project templates for xUnit.net, MSTest, and NUnit frameworks, each including a sample test to help you get started quickly.

Before you start testing your Aspire solutions, you’ll need the 📦 Aspire.Hosting.Testing NuGet package. This powerful package provides the DistributedApplicationTestingBuilder class—your gateway to creating a test host for distributed applications.

Think of the DistributedApplicationTestingBuilder as a test harness that launches your AppHost project with built-in instrumentation. This gives you precise control to access and manipulate the host throughout its lifecycle. You’ll work with familiar Aspire types like IDistributedApplicationBuilder and DistributedApplication to build and start your AppHost, making your tests feel natural and intuitive.

To create an Aspire test project, use the testing project template. When starting a new Aspire project, both IDE and CLI tooling prompts you to create a test project for some templates. To add a test project to an existing Aspire solution, use the dotnet new command:

Terminal window
dotnet new aspire-xunit -o xUnit.Tests
Terminal window
dotnet new aspire-mstest -o MSTest.Tests
Terminal window
dotnet new aspire-nunit -o NUnit.Tests

Change directory to the newly created test project:

Terminal window
cd xUnit.Tests
Terminal window
cd MSTest.Tests
Terminal window
cd NUnit.Tests

After adding the test project to your Aspire solution, add a project reference to the target AppHost. For example, if your Aspire solution contains an AppHost project named AspireApp.AppHost, add a project reference to it from the test project:

Terminal window
dotnet reference add ../AspireApp.AppHost/AspireApp.AppHost.csproj --project xUnit.Tests.csproj
Terminal window
dotnet reference add ../AspireApp.AppHost/AspireApp.AppHost.csproj --project MSTest.Tests.csproj
Terminal window
dotnet reference add ../AspireApp.AppHost/AspireApp.AppHost.csproj --project NUnit.Tests.csproj

Finally, you can uncomment out the IntegrationTest1.cs file in the test project to explore the sample test.

The following example test project was created as part of the Blazor & Minimal API starter template. If you’re unfamiliar with it, see Build your first app—C#. The Aspire test project takes a project reference dependency on the target AppHost. Consider the template project:

xUnit.Tests.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="13.0.0" />
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AspireApp.AppHost\AspireApp.AppHost.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="System.Net" />
<Using Include="Microsoft.Extensions.DependencyInjection" />
<Using Include="Aspire.Hosting.ApplicationModel" />
<Using Include="Aspire.Hosting.Testing" />
<Using Include="Xunit" />
</ItemGroup>
</Project>
MSTest.Tests.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<EnableMSTestRunner>true</EnableMSTestRunner>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="13.0.0" />
<PackageReference Include="MSTest" Version="3.10.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AspireApp.AppHost\AspireApp.AppHost.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="System.Net" />
<Using Include="Microsoft.Extensions.DependencyInjection" />
<Using Include="Aspire.Hosting.ApplicationModel" />
<Using Include="Aspire.Hosting.Testing" />
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>
</Project>
NUnit.Tests.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="13.0.0" />
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="NUnit" Version="4.4.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.10.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AspireApp.AppHost\AspireApp.AppHost.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="System.Net" />
<Using Include="Microsoft.Extensions.DependencyInjection" />
<Using Include="Aspire.Hosting.ApplicationModel" />
<Using Include="Aspire.Hosting.Testing" />
<Using Include="NUnit.Framework" />
</ItemGroup>
</Project>

The preceding project file is fairly standard. There’s a PackageReference to the 📦 Aspire.Hosting.Testing NuGet package, which includes the required types to write tests for Aspire projects.

The template test project includes an IntegrationTest1 class with a single test. The test verifies the following scenario:

  • The AppHost is successfully created and started.
  • The webfrontend resource is available and running.
  • An HTTP request can be made to the webfrontend resource and returns a successful response (HTTP 200 OK).

Consider the following test class:

IntegrationTest1.cs
using Microsoft.Extensions.Logging;
namespace xUnit.Tests.Tests;
public class IntegrationTest1
{
private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);
[Fact]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// Arrange
var cancellationToken = CancellationToken.None;
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>(cancellationToken);
appHost.Services.AddLogging(logging =>
{
logging.SetMinimumLevel(LogLevel.Debug);
// Override the logging filters from the app's configuration
logging.AddFilter(appHost.Environment.ApplicationName, LogLevel.Debug);
logging.AddFilter("Aspire.", LogLevel.Debug);
});
appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
await using var app = await appHost.BuildAsync(cancellationToken)
.WaitAsync(DefaultTimeout, cancellationToken);
await app.StartAsync(cancellationToken)
.WaitAsync(DefaultTimeout, cancellationToken);
// Act
using var httpClient = app.CreateHttpClient("webfrontend");
await app.ResourceNotifications.WaitForResourceHealthyAsync(
"webfrontend", cancellationToken)
.WaitAsync(DefaultTimeout, cancellationToken);
using var response = await httpClient.GetAsync("/", cancellationToken);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
IntegrationTest1.cs
using Microsoft.Extensions.Logging;
namespace MSTest.Tests;
[TestClass]
public class IntegrationTest1
{
public TestContext TestContext { get; set; }
private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);
[TestMethod]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// Arrange
var cancellationToken = TestContext.CancellationTokenSource.Token;
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
appHost.Services.AddLogging(logging =>
{
logging.SetMinimumLevel(LogLevel.Debug);
// Override the logging filters from the app's configuration
logging.AddFilter(appHost.Environment.ApplicationName, LogLevel.Debug);
logging.AddFilter("Aspire.", LogLevel.Debug);
});
appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
await using var app = await appHost.BuildAsync(cancellationToken)
.WaitAsync(DefaultTimeout, cancellationToken);
await app.StartAsync(cancellationToken)
.WaitAsync(DefaultTimeout, cancellationToken);
// Act
using var httpClient = app.CreateHttpClient("webfrontend");
await app.ResourceNotifications.WaitForResourceHealthyAsync(
"webfrontend", cancellationToken)
.WaitAsync(DefaultTimeout, cancellationToken);
using var response = await httpClient.GetAsync("/", cancellationToken);
// Assert
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
}
IntegrationTest1.cs
using Microsoft.Extensions.Logging;
namespace NUnit.Tests;
public class IntegrationTest1
{
private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);
[Test]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// Arrange
using var cts = new CancellationTokenSource(DefaultTimeout);
var cancellationToken = cts.Token;
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
appHost.Services.AddLogging(logging =>
{
logging.SetMinimumLevel(LogLevel.Debug);
// Override the logging filters from the app's configuration
logging.AddFilter(appHost.Environment.ApplicationName, LogLevel.Debug);
logging.AddFilter("Aspire.", LogLevel.Debug);
});
appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
await using var app = await appHost.BuildAsync(cancellationToken)
.WaitAsync(DefaultTimeout, cancellationToken);
await app.StartAsync(cancellationToken)
.WaitAsync(DefaultTimeout, cancellationToken);
// Act
using var httpClient = app.CreateHttpClient("webfrontend");
await app.ResourceNotifications.WaitForResourceHealthyAsync(
"webfrontend", cancellationToken)
.WaitAsync(DefaultTimeout, cancellationToken);
using var response = await httpClient.GetAsync("/", cancellationToken);
// Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
}
}

The preceding code:

  • Relies on the DistributedApplicationTestingBuilder.CreateAsync API to asynchronously create the AppHost.
  • The appHost is an instance of IDistributedApplicationTestingBuilder that represents the AppHost.
  • The appHost instance has its service collection configured with logging and the standard HTTP resilience handler. For more information, see Build resilient HTTP apps: Key development patterns.
  • The appHost has its BuildAsync method invoked, which returns the DistributedApplication instance as the app.
  • The app is started asynchronously.
  • An HttpClient is created for the webfrontend resource by calling app.CreateHttpClient.
  • The app.ResourceNotifications is used to wait for the webfrontend resource to be healthy.
  • A simple HTTP GET request is made to the root of the webfrontend resource.
  • The test asserts that the response status code is OK.

To further test resources and their expressed dependencies in your Aspire solution, you can assert that environment variables are injected correctly. The following example demonstrates how to test that the webfrontend resource has an HTTPS environment variable that resolves to the apiservice resource:

EnvVarTests.cs
using Aspire.Hosting;
namespace Tests;
public class EnvVarTests
{
[Fact]
public async Task WebResourceEnvVarsResolveToApiService()
{
// Arrange
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
var frontend = builder.CreateResourceBuilder<ProjectResource>("webfrontend");
// Act
var envVars = await frontend.Resource.GetEnvironmentVariableValuesAsync(
DistributedApplicationOperation.Publish);
// Assert
Assert.Contains(envVars, static (kvp) =>
{
var (key, value) = kvp;
return key is "APISERVICE_HTTPS"
&& value is "{apiservice.bindings.https.url}";
});
}
}
EnvVarTests.cs
using Aspire.Hosting;
namespace Tests;
[TestClass]
public class EnvVarTests
{
[TestMethod]
public async Task WebResourceEnvVarsResolveToApiService()
{
// Arrange
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
var frontend = builder.CreateResourceBuilder<ProjectResource>("webfrontend");
// Act
var envVars = await frontend.Resource.GetEnvironmentVariableValuesAsync(
DistributedApplicationOperation.Publish);
// Assert
CollectionAssert.Contains(envVars,
new KeyValuePair<string, string>(
key: "APISERVICE_HTTPS",
value: "{apiservice.bindings.https.url}"));
}
}
EnvVarTests.cs
using Aspire.Hosting;
namespace Tests;
public class EnvVarTests
{
[Test]
public async Task WebResourceEnvVarsResolveToApiService()
{
// Arrange
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
var frontend = builder.CreateResourceBuilder<ProjectResource>("webfrontend");
// Act
var envVars = await frontend.Resource.GetEnvironmentVariableValuesAsync(
DistributedApplicationOperation.Publish);
// Assert
Assert.That(envVars, Does.Contain(
new KeyValuePair<string, string>(
key: "APISERVICE_HTTPS",
value: "{apiservice.bindings.https.url}")));
}
}

The preceding code:

  • Relies on the DistributedApplicationTestingBuilder.CreateAsync API to asynchronously create the AppHost.
  • The builder instance is used to retrieve an IResourceWithEnvironment instance named “webfrontend” from the Resources property.
  • The webfrontend resource is used to call GetEnvironmentVariableValuesAsync to retrieve its configured environment variables.
  • The DistributedApplicationOperation.Publish argument is passed when calling GetEnvironmentVariableValuesAsync to specify environment variables that are published to the resource as binding expressions.
  • With the returned environment variables, the test asserts that the webfrontend resource has an HTTPS environment variable that resolves to the apiservice resource.

When writing tests for your Aspire solutions, you might want to capture and view logs to help with debugging and monitoring test execution. The DistributedApplicationTestingBuilder provides access to the service collection, allowing you to configure logging for your test scenarios.

To capture logs from your tests, use the AddLogging method on the builder.Services to configure logging providers specific to your testing framework:

LoggingTest.cs
using Microsoft.Extensions.Logging;
namespace Tests;
public class LoggingTest
{
[Fact]
public async Task GetWebResourceRootReturnsOkStatusCodeWithLogging()
{
// Arrange
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
builder.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
// Configure logging to capture test execution logs
builder.Services.AddLogging(logging => logging
.AddConsole() // Outputs logs to console
.AddFilter("Default", LogLevel.Information)
.AddFilter("Microsoft.AspNetCore", LogLevel.Warning)
.AddFilter("Aspire.Hosting.Dcp", LogLevel.Warning));
await using var app = await builder.BuildAsync();
await app.StartAsync();
// Act
var httpClient = app.CreateHttpClient("webfrontend");
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await app.ResourceNotifications.WaitForResourceHealthyAsync(
"webfrontend",
cts.Token);
var response = await httpClient.GetAsync("/");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
LoggingTest.cs
using Microsoft.Extensions.Logging;
namespace Tests;
[TestClass]
public class LoggingTest
{
[TestMethod]
public async Task GetWebResourceRootReturnsOkStatusCodeWithLogging()
{
// Arrange
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
builder.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
// Configure logging to capture test execution logs
builder.Services.AddLogging(logging => logging
.AddConsole() // Outputs logs to console
.AddFilter("Default", LogLevel.Information)
.AddFilter("Microsoft.AspNetCore", LogLevel.Warning)
.AddFilter("Aspire.Hosting.Dcp", LogLevel.Warning));
await using var app = await builder.BuildAsync();
await app.StartAsync();
// Act
var httpClient = app.CreateHttpClient("webfrontend");
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await app.ResourceNotifications.WaitForResourceHealthyAsync(
"webfrontend",
cts.Token);
var response = await httpClient.GetAsync("/");
// Assert
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
}
LoggingTest.cs
using Microsoft.Extensions.Logging;
namespace Tests;
public class LoggingTest
{
[Test]
public async Task GetWebResourceRootReturnsOkStatusCodeWithLogging()
{
// Arrange
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
builder.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
// Configure logging to capture test execution logs
builder.Services.AddLogging(logging => logging
.AddConsole() // Outputs logs to console
.AddFilter("Default", LogLevel.Information)
.AddFilter("Microsoft.AspNetCore", LogLevel.Warning)
.AddFilter("Aspire.Hosting.Dcp", LogLevel.Warning));
await using var app = await builder.BuildAsync();
await app.StartAsync();
// Act
var httpClient = app.CreateHttpClient("webfrontend");
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await app.ResourceNotifications.WaitForResourceHealthyAsync(
"webfrontend",
cts.Token);
var response = await httpClient.GetAsync("/");
// Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
}
}

Since the appsettings.json configuration from your application isn’t automatically replicated in test projects, you need to explicitly configure log filters. This is important to avoid excessive logging from infrastructure components that might overwhelm your test output. The following snippet explicitly configures log filters:

builder.Services.AddLogging(logging => logging
.AddFilter("Default", LogLevel.Information)
.AddFilter("Microsoft.AspNetCore", LogLevel.Warning)
.AddFilter("Aspire.Hosting.Dcp", LogLevel.Warning));

The preceding configuration:

  • Sets the default log level to Information for most application logs.
  • Reduces noise from ASP.NET Core infrastructure by setting it to Warning level.
  • Limits Aspire hosting infrastructure logs to Warning level to focus on application-specific logs.

Different testing frameworks have different logging provider packages available to assist with managing logging during test execution:

xUnit.net doesn’t capture log output from tests as test output. Tests must use the ITestOutputHelper interface to achieve this.

For xUnit.net, consider using one of these logging packages:

For MSTest, consider using one of these logging packages:

For NUnit, consider using one of these logging packages:

The Aspire testing project template makes it easier to create test projects for Aspire solutions. The template project includes a sample test that you can use as a starting point for your tests. The DistributedApplicationTestingBuilder follows a familiar pattern to the WebApplicationFactory<T> in ASP.NET Core. It allows you to create a test host for your distributed application and run tests against it.

Finally, when using the DistributedApplicationTestingBuilder all resource logs are redirected to the DistributedApplication by default. The redirection of resource logs enables scenarios where you want to assert that a resource is logging correctly.

Вопросы & ответыСотрудничатьСообществоОбсуждатьСмотреть