Scrivi il tuo primo test .NET.NET Aspire
Questo articolo illustra come creare un progetto di test, scrivere test ed eseguirli per le soluzioni .NET.NET Aspire. I test in questo articolo non sono unit test, ma piuttosto test funzionali o di integrazione. .NET .NET Aspire include diverse varianti dei modelli di progetto di test che è possibile usare per testare le dipendenze delle risorse .NET.NET Aspire e le relative comunicazioni. I modelli di progetto di test sono disponibili per i framework di test MSTest, NUnit e xUnit e includono un test di esempio che è possibile usare come punto di partenza per i test.
I modelli di progetto di test .NET.NET Aspire si basano sul pacchetto NuGet 📦Aspire.Hosting.Testing. Questo pacchetto espone la classe DistributedApplicationTestingBuilder, usata per creare un host di test per l'applicazione distribuita. Il generatore di test delle applicazioni distribuite si basa sulla classe DistributedApplication per creare e avviare l'host dell'app .
Creare un progetto di test
Il modo più semplice per creare un progetto di test .NET.NET Aspire consiste nell'usare il modello di progetto di test. Se si avvia un nuovo progetto di .NET.NET Aspire e si desidera includere progetti di test, gli strumenti di Visual Studio supportano tale opzione. Se si aggiunge un progetto di test a un progetto di .NET.NET Aspire esistente, è possibile usare il comando dotnet new
per creare un progetto di test:
dotnet new aspire-xunit
dotnet new aspire-mstest
dotnet new aspire-nunit
Per ulteriori informazioni, vedere la documentazione del comando CLI .NETdotnet new.
Esplorare il progetto di test
Il seguente progetto di test di esempio è stato creato come parte del modello Starter Application .NET.NET Aspire. Se non ne hai familiarità, consulta Avvio rapido: Crea il tuo primo progetto .NET.NET Aspire. Il progetto di test .NET.NET Aspire accetta una dipendenza di riferimento del progetto dall'host dell'app di destinazione. Si consideri il progetto modello:
<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.3" />
</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>
Il file di progetto precedente è piuttosto standard. Esiste un PackageReference
nella 📦Aspire. Hosting.Testing NuGet package, che include le classi necessarie per scrivere test per i progetti .NET.NET Aspire.
Il progetto di test del modello include una classe IntegrationTest1
con un singolo test. Il test verifica lo scenario seguente:
- L'host dell'app viene creato e avviato correttamente.
- La risorsa
webfrontend
è disponibile e in esecuzione. - Una richiesta HTTP può essere inviata alla risorsa
webfrontend
e restituisce una risposta riuscita (HTTP 200 OK).
Si consideri la classe di test seguente:
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));
}
}
Il codice precedente:
- Si basa sull'API DistributedApplicationTestingBuilder.CreateAsync per creare in modo asincrono l'host dell'app.
- Il
appHost
è un'istanza diIDistributedApplicationTestingBuilder
che rappresenta l'host dell'app. - L'istanza
appHost
ha la sua raccolta di servizi configurata con il gestore standard per la resilienza HTTP. Per altre informazioni, vedere Creare app HTTP resilienti: Modelli di sviluppo chiave.
- Il
- L'
appHost
ha il metodo IDistributedApplicationTestingBuilder.BuildAsync(CancellationToken) richiamato, che restituisce l'istanza diDistributedApplication
comeapp
.- L'
app
ha il provider di servizi che ottiene l'istanza di ResourceNotificationService. - Il
app
viene avviato in modo asincrono.
- L'
- Viene creato un HttpClient per la risorsa
webfrontend
chiamandoapp.CreateHttpClient
. - Il
resourceNotificationService
viene usato per attendere che la risorsawebfrontend
sia disponibile ed in esecuzione. - Viene effettuata una semplice richiesta HTTP GET alla radice della risorsa
webfrontend
. - Il test afferma che il codice di stato della risposta è
OK
.
Testare le variabili di ambiente delle risorse
Per testare ulteriormente le risorse e le relative dipendenze espresse nella soluzione .NET.NET Aspire, è possibile affermare che le variabili di ambiente vengono inserite correttamente. L'esempio seguente illustra come verificare che la risorsa webfrontend
abbia una variabile di ambiente HTTPS che si risolve nella risorsa 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}")));
}
}
Il codice precedente:
- Si basa sull'API DistributedApplicationTestingBuilder.CreateAsync per creare in modo asincrono l'host dell'app.
- L'istanza di
builder
viene usata per recuperare un'istanza di IResourceWithEnvironment denominata "webfrontend" dal IDistributedApplicationTestingBuilder.Resources. - La risorsa
webfrontend
viene usata per chiamare GetEnvironmentVariableValuesAsync per recuperare le variabili di ambiente configurate. - L'argomento DistributedApplicationOperation.Publish viene passato quando si chiama
GetEnvironmentVariableValuesAsync
per specificare le variabili di ambiente pubblicate nella risorsa come espressioni di associazione. - Con le variabili di ambiente restituite, il test afferma che la risorsa
webfrontend
ha una variabile di ambiente HTTPS che viene risolta nella risorsaapiservice
.
Sommario
Il modello di progetto di test .NET Aspire semplifica la creazione di progetti di test per le soluzioni .NET Aspire. Il progetto modello include un test di esempio che è possibile usare come punto di partenza per i test. Il DistributedApplicationTestingBuilder
segue uno schema noto a WebApplicationFactory<TEntryPoint> in ASP.NET Core. Consente di creare un host di test per l'applicazione distribuita ed eseguirne i test.
Infine, quando si usa il DistributedApplicationTestingBuilder
tutti i log delle risorse vengono reindirizzati alla DistributedApplication
per impostazione predefinita. Il reindirizzamento dei log delle risorse consente scenari in cui si vuole verificare che una risorsa stia registrando correttamente.