Dela via


Skriv ditt första Aspire-test

I den här artikeln får du lära dig hur du skapar ett testprojekt, skriver tester och kör dem för dina Aspire lösningar. Testerna i den här artikeln är inte enhetstester, utan snarare funktions- eller integreringstester. Aspire innehåller flera varianter av testprojektmallar som du kan använda för att testa dina Aspire resursberoenden – och deras kommunikation. Testprojektmallarna är tillgängliga för MSTest, NUnit och xUnit.net testramverk och innehåller ett exempeltest som du kan använda som utgångspunkt för dina tester.

Testprojektmallarna Aspire förlitar sig på 📦Aspire. Hosting.Testing NuGet-paket. Det här paketet exponerar klassen DistributedApplicationTestingBuilder, som används för att skapa en testvärd för ditt distribuerade program. Den distribuerade programtestverktyget startar ditt AppHost-projekt med instrumentationskrokar så att du kan komma åt och manipulera värden i olika skeden av dess livslängd. I synnerhet DistributedApplicationTestingBuilder ger dig åtkomst till IDistributedApplicationBuilder och DistributedApplication klass för att skapa och starta AppHost.

Skapa ett testprojekt

Det enklaste sättet att skapa ett Aspire testprojekt är att använda testprojektmallen. Om du startar ett nytt Aspire projekt och vill inkludera testprojekt stöder verktygenVisual Studio det alternativet. Om du lägger till ett testprojekt i ett befintligt Aspire projekt kan du använda dotnet new kommandot för att skapa ett testprojekt:

dotnet new aspire-xunit
dotnet new aspire-mstest
dotnet new aspire-nunit

För mer information, se dokumentationen för kommandot .NET CLI dotnet new.

Utforska testprojektet

Följande exempeltestprojekt skapades som en del av mallen Aspire Startprogram . Om du inte känner till det kan du läsa Snabbstart: Skapa ditt första Aspire projekt. Testprojektet Aspire har ett projektreferensberoende av den målhostade AppHost. Överväg mallprojektet:

<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.2" />
    <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.2" />
    <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.2" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.10" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
    <PackageReference Include="NUnit" Version="4.4.0" />
    <PackageReference Include="NUnit.Analyzers" Version="4.11.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>

Den föregående projektfilen är ganska standard. Det finns en PackageReference till 📦Aspire.Hosting.Testing NuGet-paketet, som innehåller de typer som krävs för att skriva tester för Aspire projekt.

Malltestprojektet innehåller en IntegrationTest1-klass med ett enda test. Testet verifierar följande scenario:

  • AppHost har skapats och startats.
  • webfrontend-resursen är tillgänglig och körs.
  • En HTTP-begäran kan göras till den webfrontend resursen och returnerar ett lyckat svar (HTTP 200 OK).

Överväg följande testklass:

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));
    }
}

Föregående kod:

Testa resursmiljövariabler

För att ytterligare testa resurser och deras uttryckta beroenden i din Aspire lösning kan du kontrollera att miljövariablerna matas in korrekt. I följande exempel visas hur du testar att den webfrontend resursen har en HTTPS-miljövariabel som matchar den apiservice resursen:

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}")));
    }
}

Föregående kod:

Samla in loggar från tester

När du skriver tester för dina Aspire lösningar kanske du vill samla in och visa loggar för att hjälpa till med felsökning och övervakning av testkörning. DistributedApplicationTestingBuilder Ger åtkomst till tjänstsamlingen så att du kan konfigurera loggning för dina testscenarier.

Konfigurera loggningsleverantörer

Om du vill samla in loggar från dina tester använder du AddLogging metoden på builder.Services för att konfigurera loggningsproviders som är specifika för ditt testramverk:

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));
    }
}

Konfigurera loggfilter

Eftersom appsettings.json konfigurationen från ditt program inte replikeras automatiskt i testprojekt måste du uttryckligen konfigurera loggfilter. Detta är viktigt för att undvika överdriven loggning från infrastrukturkomponenter som kan överbelasta dina testutdata. Följande kodfragment konfigurerar uttryckligen loggfilter:

builder.Services.AddLogging(logging => logging
    .AddFilter("Default", LogLevel.Information)
    .AddFilter("Microsoft.AspNetCore", LogLevel.Warning)
    .AddFilter("Aspire.Hosting.Dcp", LogLevel.Warning));

Föregående konfiguration:

  • Anger standardloggnivån till Information för de flesta programloggar.
  • Minskar bruset från ASP.NET Core infrastrukturen genom att ställa in den på Warning nivå.
  • Begränsar Aspire värdinfrastrukturloggar till Warning nivå för att fokusera på programspecifika loggar.

Olika testramverk har olika paket för loggningsprovider som hjälper dig att hantera loggning under testkörningen:

xUnit.net samlar inte in loggutdata från tester som testutdata. Testerna måste använda ITestOutputHelper gränssnittet för att uppnå detta.

Överväg att använda något av följande loggningspaket för xUnit.net:

För MSTest bör du överväga att använda något av följande loggningspaket:

För NUnit bör du överväga att använda något av följande loggningspaket:

Sammanfattning

Den Aspire testprojektmallen gör det enklare att skapa testprojekt för Aspire lösningar. Mallprojektet innehåller ett exempeltest som du kan använda som utgångspunkt för dina tester. DistributedApplicationTestingBuilder följer ett välbekant mönster för WebApplicationFactory<TEntryPoint> i ASP.NET Core. Det gör att du kan skapa en testvärd för ditt distribuerade program och köra tester mot den.

När du använder DistributedApplicationTestingBuilder omdirigeras slutligen alla resursloggar till DistributedApplication som standard. Omdirigeringen av resursloggar möjliggör scenarier där du vill kontrollera att en resurs loggar korrekt.

Se även