Condividi tramite


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 di IDistributedApplicationTestingBuilder 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.
  • L'appHost ha il metodo IDistributedApplicationTestingBuilder.BuildAsync(CancellationToken) richiamato, che restituisce l'istanza di DistributedApplication come app.
  • Viene creato un HttpClient per la risorsa webfrontend chiamando app.CreateHttpClient.
  • Il resourceNotificationService viene usato per attendere che la risorsa webfrontend 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:

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.

Vedere anche