Napisz swój pierwszy test .NET.NET Aspire
Z tego artykułu dowiesz się, jak utworzyć projekt testowy, napisać testy i uruchomić je dla rozwiązań .NET.NET Aspire. Testy w tym artykule nie są testami jednostkowym, ale raczej funkcjonalnymi ani testami integracyjnymi. .NET .NET Aspire zawiera kilka odmian szablonów projektów testowania , których można użyć do testowania zależności zasobów .NET.NET Aspire i ich komunikacji. Szablony projektów testowych są dostępne dla platform testowych MSTest, NUnit i xUnit oraz zawierają przykładowy test, którego można użyć jako punktu wyjścia dla testów.
Szablony projektów testowych .NET.NET Aspire opierają się na pakiecie NuGet 📦Aspire.Hosting.Testing. Ten pakiet uwidacznia klasę DistributedApplicationTestingBuilder, która służy do tworzenia hosta testowego dla aplikacji rozproszonej. Konstruktor testowania aplikacji rozproszonych opiera się na klasie DistributedApplication w celu utworzenia i uruchomienia hosta aplikacji .
Tworzenie projektu testowego
Najprostszym sposobem utworzenia projektu testowego .NET.NET Aspire jest użycie szablonu projektu testowego. Jeśli uruchamiasz nowy projekt .NET.NET Aspire i chcesz uwzględnić projekty testowe, narzędzia Visual Studio obsługują tę opcję. Jeśli dodasz projekt testowy do istniejącego projektu .NET.NET Aspire, możesz użyć polecenia dotnet new
, aby utworzyć projekt testowy:
dotnet new aspire-xunit
dotnet new aspire-mstest
dotnet new aspire-nunit
Aby uzyskać więcej informacji, zobacz dokumentację polecenia dotnet new dla
Eksplorowanie projektu testowego
Poniższy przykładowy projekt testowy został utworzony jako część szablonu aplikacji początkowej
<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>
Powyższy plik projektu jest dość standardowy. Jest PackageReference
do 📦Aspirepakiet .Hosting.Testing NuGet zawierający wymagane typy do pisania testów dla projektów .NET.NET Aspire.
Projekt testowy szablonu zawiera klasę IntegrationTest1
z jednym testem. Test sprawdza następujący scenariusz:
- Host aplikacji został pomyślnie utworzony i uruchomiony.
- Zasób
webfrontend
jest dostępny i uruchomiony. - Żądanie HTTP można wysłać do zasobu
webfrontend
i zwrócić pomyślną odpowiedź (HTTP 200 OK).
Rozważmy następującą klasę testowa:
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));
}
}
Powyższy kod:
- Korzysta z interfejsu API DistributedApplicationTestingBuilder.CreateAsync do asynchronicznego tworzenia hosta aplikacji.
-
appHost
jest wystąpieniemIDistributedApplicationTestingBuilder
reprezentującym hosta aplikacji. - Wystąpienie
appHost
ma kolekcję usług skonfigurowaną przy użyciu standardowego mechanizmu odporności HTTP. Aby uzyskać więcej informacji, zobacz Tworzenie odpornych aplikacji HTTP: Kluczowe wzorce programistyczne.
-
- Metoda IDistributedApplicationTestingBuilder.BuildAsync(CancellationToken) w
appHost
jest wywoływana, co zwraca instancjęDistributedApplication
jakoapp
.- Dostawca usług
app
uzyskuje instancję ResourceNotificationService. -
app
jest uruchamiane asynchronicznie.
- Dostawca usług
- Za pomocą wywołania
app.CreateHttpClient
tworzony jest HttpClient dla zasobuwebfrontend
. -
resourceNotificationService
służy do oczekiwania na dostępność i uruchomienie zasobuwebfrontend
. - Proste żądanie HTTP GET jest wykonywane w katalogu głównym zasobu
webfrontend
. - Test potwierdza, że kod stanu odpowiedzi jest
OK
.
Testowanie zmiennych środowiskowych zasobów
Aby dodatkowo przetestować zasoby i ich wyrażone zależności w rozwiązaniu .NET.NET Aspire, możesz potwierdzić, że zmienne środowiskowe są poprawnie wstrzykiwane. W poniższym przykładzie pokazano, jak przetestować, czy zasób webfrontend
ma zmienną środowiskową HTTPS, która odnosi się do zasobu 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}")));
}
}
Powyższy kod:
- Korzysta z interfejsu API DistributedApplicationTestingBuilder.CreateAsync do asynchronicznego tworzenia hosta aplikacji.
- Wystąpienie
builder
służy do pobierania wystąpienia IResourceWithEnvironment o nazwie "webfrontend" z IDistributedApplicationTestingBuilder.Resources. - Zasób
webfrontend
służy do wywoływania GetEnvironmentVariableValuesAsync w celu pobrania skonfigurowanych zmiennych środowiskowych. - Argument DistributedApplicationOperation.Publish jest przekazywany podczas wywoływania
GetEnvironmentVariableValuesAsync
w celu określenia zmiennych środowiskowych publikowanych w zasobie jako wyrażeń powiązań. - W przypadku zwracanych zmiennych środowiskowych test potwierdza, że zasób
webfrontend
ma zmienną środowiskową HTTPS, która rozpoznaje zasóbapiservice
.
Streszczenie
Szablon projektu testowania .NET Aspire ułatwia tworzenie projektów testowych dla rozwiązań .NET Aspire. Projekt szablonu zawiera przykładowy test, którego można użyć jako punktu wyjścia dla testów.
DistributedApplicationTestingBuilder
jest zgodny ze znanym wzorcem WebApplicationFactory<TEntryPoint> w ASP.NET Core. Umożliwia tworzenie hosta testowego dla aplikacji rozproszonej i uruchamianie testów względem niej.
Na koniec podczas korzystania z DistributedApplicationTestingBuilder
wszystkie dzienniki zasobów są domyślnie przekierowywane do DistributedApplication
. Przekierowywanie dzienników zasobów umożliwia tworzenie scenariuszy, w których chcesz potwierdzić, że zasób jest poprawnie rejestrowany.