本文介绍如何为您的Aspire解决方案创建测试项目、编写测试并运行它们。 本文中的测试不是单元测试,而是功能测试或集成测试。 Aspire 包括测试 项目模板 的多个变体,可用于测试 Aspire 资源依赖项及其通信。 测试项目模板适用于 MSTest、NUnit 和 xUnit.net 测试框架,并包括一个示例测试,可用于测试的起点。
测试 Aspire 项目模板依赖于 📦Aspire.Hosting.Testing NuGet 包。 此包公开 DistributedApplicationTestingBuilder 类,该类用于为分布式应用程序创建测试主机。 分布式应用程序测试生成器使用检测钩子启动 AppHost 项目,以便可以在其生命周期的各个阶段访问和操控主机。 具体而言, DistributedApplicationTestingBuilder 你可以访问 IDistributedApplicationBuilder 和 DistributedApplication 类来创建和启动 AppHost。
创建测试项目
创建 Aspire 测试项目的最简单方法是使用测试项目模板。 如果要启动一个新Aspire项目并想要包含测试项目,该工具Visual Studio支持该选项。 如果要将测试项目添加到现有 Aspire 项目,可以使用 dotnet new
命令创建测试项目:
dotnet new aspire-xunit
dotnet new aspire-mstest
dotnet new aspire-nunit
有关详细信息,请参阅 .NET CLI dotnet new 命令文档。
浏览测试项目
以下示例测试项目已创建为初学者应用程序模板的Aspire一部分。 如果不熟悉它,请参阅快速入门:生成第一个项目Aspire。 测试 Aspire 项目对目标 AppHost 采用项目引用依赖项。 请考虑模板项目:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="9.5.1" />
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.10" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
</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>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<PropertyGroup>
<EnableMSTestRunner>true</EnableMSTestRunner>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="9.5.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.10" />
<PackageReference Include="MSTest" Version="4.0.1" />
</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>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="9.5.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.9" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageReference Include="NUnit" Version="4.4.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.10.0" />
<PackageReference Include="NUnit3TestAdapter" Version="5.2.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>
上述项目文件相当标准。 有一个 PackageReference
到 📦AspireHosting.Testing NuGet 包,其中包括为 Aspire 项目编写测试所需的类型。
模板测试项目包含具有单个测试的 IntegrationTest1
类。 该测试验证以下方案:
- 已成功创建并启动 AppHost。
-
webfrontend
资源可用且正在运行。 - 可以向
webfrontend
资源发出 HTTP 请求,并返回成功的响应(HTTP 200 正常)。
请考虑以下测试类:
namespace AspireApp.Tests;
public class IntegrationTest1
{
[Fact]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// Arrange
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
builder.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
// To capture logs from your tests, see the "Capture logs from tests" section
// in the documentation or refer to LoggingTest.cs for a complete example
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);
}
}
namespace AspireApp.Tests;
[TestClass]
public class IntegrationTest1
{
[TestMethod]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// Arrange
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
builder.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
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);
}
}
namespace AspireApp.Tests;
public class IntegrationTest1
{
[Test]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// Arrange
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
builder.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
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));
}
}
前面的代码:
- 依赖于 DistributedApplicationTestingBuilder.CreateAsync API 异步创建 AppHost。
-
appHost
是一个表示 AppHost 的IDistributedApplicationTestingBuilder
实例。 -
appHost
实例使用标准 HTTP 复原处理程序配置其服务集合。 有关详细信息,请参阅 生成可复原 HTTP 应用:关键开发模式。
-
-
appHost
调用 IDistributedApplicationTestingBuilder.BuildAsync(CancellationToken) 方法,该方法将DistributedApplication
实例作为app
返回。-
app
由服务提供商来获得 ResourceNotificationService 实例。 -
app
以异步方式启动。
-
- 通过调用 HttpClient为
webfrontend
资源创建app.CreateHttpClient
。 -
resourceNotificationService
用于等待webfrontend
资源可用且正在运行。 - 对
webfrontend
资源的根目录发出简单的 HTTP GET 请求。 - 测试断言该响应状态代码为
OK
。
测试资源环境变量
若要进一步测试解决方案中的 Aspire 资源及其表示的依赖项,可以断言已正确注入环境变量。 以下示例演示如何测试 webfrontend
资源是否有解析为 apiservice
资源的 HTTPS 环境变量:
using Aspire.Hosting;
namespace AspireApp.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 "services__apiservice__https__0"
&& value is "{apiservice.bindings.https.url}";
});
}
}
using Aspire.Hosting;
namespace AspireApp.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: "services__apiservice__https__0",
value: "{apiservice.bindings.https.url}"));
}
}
using Aspire.Hosting;
namespace AspireApp.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: "services__apiservice__https__0",
value: "{apiservice.bindings.https.url}")));
}
}
前面的代码:
- 依赖于 DistributedApplicationTestingBuilder.CreateAsync API 异步创建 AppHost。
-
builder
实例用于从 IResourceWithEnvironment中检索名为“webfrontend”的 IDistributedApplicationTestingBuilder.Resources 实例。 -
webfrontend
资源用于调用 GetEnvironmentVariableValuesAsync 以检索其配置的环境变量。 - 调用 DistributedApplicationOperation.Publish 以将发布到资源的环境变量指定为绑定表达式时,将传递
GetEnvironmentVariableValuesAsync
参数。 - 使用返回的环境变量,测试断言
webfrontend
资源具有解析为apiservice
资源的 HTTPS 环境变量。
从测试中捕获日志
为 Aspire 解决方案编写测试时,可能需要捕获和查看日志以帮助调试和监视测试执行。 提供 DistributedApplicationTestingBuilder
对服务集合的访问权限,允许为测试方案配置日志记录。
配置日志记录提供程序
若要从测试中捕获日志,请在 AddLogging
上使用 builder.Services
配置与测试框架相关的日志记录提供程序。
using Microsoft.Extensions.Logging;
namespace AspireApp.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);
}
}
using Microsoft.Extensions.Logging;
namespace AspireApp.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);
}
}
using Microsoft.Extensions.Logging;
namespace AspireApp.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));
}
}
配置日志筛选器
由于应用程序 中的appsettings.json 配置不会在测试项目中自动复制,需要显式配置日志筛选器。 这点很重要,以避免基础设施组件的日志记录过度,这可能会使测试输出不堪重负。 以下代码片段显式配置日志筛选器:
builder.Services.AddLogging(logging => logging
.AddFilter("Default", LogLevel.Information)
.AddFilter("Microsoft.AspNetCore", LogLevel.Warning)
.AddFilter("Aspire.Hosting.Dcp", LogLevel.Warning));
上述配置:
- 将大多数应用程序日志的默认日志级别设置为
Information
。 - 通过将ASP.NET Core基础设施设置为
Warning
级别来降低噪声。 - 将托管基础结构日志限制 Aspire 为
Warning
级别,以专注于特定于应用程序的日志。
常用日志记录包
不同的测试框架具有不同的日志记录提供程序包,可帮助在测试执行期间管理日志记录:
xUnit.net 不会将测试的日志输出捕获为测试输出。 测试必须使用ITestOutputHelper
接口来实现此目的。
对于 xUnit.net,请考虑使用以下日志记录包之一:
-
📦 MartinCostello.Logging.XUnit - 将日志输出
ILogger
到ITestOutputHelper
输出。 - 📦 Xunit.DependencyInjection.Logging - 与 xUnit.net 的依赖项注入集成。
- 📦 Serilog.Extensions.Logging.File - 将日志写入文件。
- 📦 Microsoft.Extensions.Logging.Console - 将日志输出到控制台。
对于 MSTest,请考虑使用以下日志记录包之一:
- 📦 Serilog.Extensions.Logging.File - 将日志写入文件。
- 📦 Microsoft.Extensions.Logging.Console - 将日志输出到控制台。
对于 NUnit,请考虑使用以下日志记录包之一:
- 📦 Extensions.Logging.NUnit - 与 NUnit 框架集成。
- 📦 Serilog.Extensions.Logging.File - 将日志写入文件。
- 📦 Microsoft.Extensions.Logging.Console - 将日志输出到控制台。
总结
通过 Aspire 测试项目模板,可以更轻松地为 Aspire 解决方案创建测试项目。 模板项目包括一个示例测试,可以用作你的测试的起点。
DistributedApplicationTestingBuilder
遵循 WebApplicationFactory<TEntryPoint>中 ASP.NET Core 的熟悉模式。 它允许你为分布式应用程序创建测试主机,并针对它运行测试。
最后,使用 DistributedApplicationTestingBuilder
默认情况下会将所有资源日志重定向到 DistributedApplication
。 资源日志的重定向可以用于验证资源日志记录是否正确的情形。
另请参阅
- 使用 dotnet test 和 xUnit 在
中 单元测试 C# - MSTest 概述
- 使用 NUnit 和
Core 单元测试 C#