Напишите свой первый тест .NET.NET Aspire
В этой статье вы узнаете, как создать тестовый проект, написать тесты и запустить их для .NET.NET Aspire решений. Тесты в этой статье — это не модульные тесты, а скорее функциональные или интеграционные тесты. .NET .NET Aspire включает несколько вариантов шаблонов проектов тестирования , которые можно использовать для тестирования зависимостей .NET.NET Aspire ресурсов и их взаимодействия. Шаблоны проектов тестирования доступны для платформ тестирования MSTest, NUnit и xUnit и включают пример теста, который можно использовать в качестве отправной точки для тестов.
Тестовые шаблоны проектов .NET.NET Aspire зависят от пакета NuGet 📦Aspire.Hosting.Testing. Этот пакет предоставляет класс DistributedApplicationTestingBuilder, который используется для создания тестового узла для распределенного приложения. Построитель распределенного тестирования приложений использует класс DistributedApplication для создания и запуска узла приложения.
Создание тестового проекта
Самый простой способ создать тестовый проект .NET.NET Aspire — использовать шаблон тестового проекта. Если вы запускаете новый проект .NET.NET Aspire и хотите включить тестовые проекты, средство Visual Studio поддерживает этот параметр. Если вы добавляете тестовый проект в существующий проект .NET.NET Aspire, можно использовать команду dotnet new
для создания тестового проекта:
dotnet new aspire-xunit
dotnet new aspire-mstest
dotnet new aspire-nunit
Дополнительные сведения см. в документации команды .NET CLI dotnet new.
Изучение тестового проекта
Данный пример тестового проекта был создан в составе шаблона начального приложения
<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.0.0" />
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.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="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.0.0" />
<PackageReference Include="MSTest" Version="3.7.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>
<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.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="NUnit.Analyzers" Version="4.6.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>
Предыдущий файл проекта довольно стандартный. В составе PackageReference
и 📦Aspireнаходится пакет NuGet.Hosting.Testing, который включает необходимые типы для написания тестов для проектов .NET.NET Aspire.
Проект теста шаблона включает класс IntegrationTest1
с одним тестом. Тест проверяет следующий сценарий:
- Хост приложения успешно создан и запущен.
- Ресурс
webfrontend
доступен и запущен. - К ресурсу
webfrontend
можно выполнить HTTP-запрос, который возвратит успешный ответ (HTTP 200 OK).
Рассмотрим следующий тестовый класс:
namespace AspireApp.Tests;
public class IntegrationTest1
{
[Fact]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// Arrange
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
// To output logs to the xUnit.net ITestOutputHelper,
// consider adding a package from https://www.nuget.org/packages?q=xunit+logging
await using var app = await appHost.BuildAsync();
var resourceNotificationService = app.Services
.GetRequiredService<ResourceNotificationService>();
await app.StartAsync();
// Act
var httpClient = app.CreateHttpClient("webfrontend");
await resourceNotificationService.WaitForResourceAsync(
"webfrontend",
KnownResourceStates.Running
)
.WaitAsync(TimeSpan.FromSeconds(30));
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 appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
await using var app = await appHost.BuildAsync();
var resourceNotificationService = app.Services
.GetRequiredService<ResourceNotificationService>();
await app.StartAsync();
// Act
var httpClient = app.CreateHttpClient("webfrontend");
await resourceNotificationService.WaitForResourceAsync(
"webfrontend",
KnownResourceStates.Running
)
.WaitAsync(TimeSpan.FromSeconds(30));
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 appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
await using var app = await appHost.BuildAsync();
var resourceNotificationService = app.Services
.GetRequiredService<ResourceNotificationService>();
await app.StartAsync();
// Act
var httpClient = app.CreateHttpClient("webfrontend");
await resourceNotificationService.WaitForResourceAsync(
"webfrontend",
KnownResourceStates.Running
)
.WaitAsync(TimeSpan.FromSeconds(30));
var response = await httpClient.GetAsync("/");
// Assert
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
}
}
Предыдущий код:
- Использует api DistributedApplicationTestingBuilder.CreateAsync для асинхронного создания узла приложения.
-
appHost
— это экземплярIDistributedApplicationTestingBuilder
, представляющий узел приложения. - Экземпляр
appHost
имеет свою коллекцию служб, настроенную с помощью стандартного обработчика устойчивости HTTP. Дополнительные сведения см. в статье Создание устойчивых HTTP-приложений: основные паттерны разработки.
-
-
appHost
вызывает метод IDistributedApplicationTestingBuilder.BuildAsync(CancellationToken), который возвращает экземплярDistributedApplication
в качествеapp
.-
app
предоставляет своему поставщику услуг задачу получения экземпляра ResourceNotificationService. -
app
запускается асинхронно.
-
- Для ресурса HttpClient создается
webfrontend
путем вызоваapp.CreateHttpClient
. -
resourceNotificationService
используется для ожидания доступности и запуска ресурсаwebfrontend
. - Делается простой HTTP-запрос GET к корню ресурса
webfrontend
. - Тест утверждает, что код состояния ответа
OK
.
Переменные среды тестирования ресурсов
Для дальнейшего тестирования ресурсов и их выраженных зависимостей в решении .NET.NET Aspire можно подтвердить правильность внедрения переменных среды. В следующем примере показано, как проверить, что ресурс webfrontend
имеет переменную среды HTTPS, которая ссылается на ресурс apiservice
:
using Aspire.Hosting;
namespace AspireApp.Tests;
public class EnvVarTests
{
[Fact]
public async Task WebResourceEnvVarsResolveToApiService()
{
// Arrange
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
var frontend = (IResourceWithEnvironment)appHost.Resources
.Single(static r => r.Name == "webfrontend");
// Act
var envVars = await frontend.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 appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
var frontend = (IResourceWithEnvironment)appHost.Resources
.Single(static r => r.Name == "webfrontend");
// Act
var envVars = await frontend.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 appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
var frontend = (IResourceWithEnvironment)appHost.Resources
.Single(static r => r.Name == "webfrontend");
// Act
var envVars = await frontend.GetEnvironmentVariableValuesAsync(
DistributedApplicationOperation.Publish);
// Assert
Assert.That(envVars, Does.Contain(
new KeyValuePair<string, string>(
key: "services__apiservice__https__0",
value: "{apiservice.bindings.https.url}")));
}
}
Предыдущий код:
- Использует api DistributedApplicationTestingBuilder.CreateAsync для асинхронного создания узла приложения.
- Экземпляр
builder
используется для извлечения экземпляра IResourceWithEnvironment, названного "webfrontend", из IDistributedApplicationTestingBuilder.Resources. - Ресурс
webfrontend
используется для вызова GetEnvironmentVariableValuesAsync для получения настроенных переменных среды. - Аргумент DistributedApplicationOperation.Publish передается при вызове
GetEnvironmentVariableValuesAsync
для указания переменных среды, опубликованных в ресурсе в качестве выражений привязки. - С возвращёнными переменными среды тест утверждает, что ресурс
webfrontend
имеет переменную среды HTTPS, которая разрешается в ресурсapiservice
.
Сводка
Шаблон проекта тестирования .NET Aspire упрощает создание тестовых проектов для решений .NET Aspire. Проект шаблона включает пример теста, который можно использовать в качестве отправной точки для тестов.
DistributedApplicationTestingBuilder
следует знакомому шаблону WebApplicationFactory<TEntryPoint> в ASP.NET Core. Он позволяет создать тестовый узел для распределенного приложения и выполнить тесты с ним.
Наконец, при использовании DistributedApplicationTestingBuilder
все журналы ресурсов перенаправляются на DistributedApplication
по умолчанию. Перенаправление журналов ресурсов позволяет сценариям, в которых требуется подтвердить правильность ведения журнала ресурса.
См. также
.NET Aspire