Udostępnij za pośrednictwem


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 CLI .

Eksplorowanie projektu testowego

Poniższy przykładowy projekt testowy został utworzony jako część szablonu aplikacji początkowej . Jeśli nie jesteś zaznajomiony z tematem, zobacz krótki przewodnik : Tworzenie pierwszego projektu .NET.NET Aspire. Projekt testowy .NET.NET Aspire przyjmuje zależność referencyjną projektu od hosta aplikacji docelowej. Rozważ projekt szablonu:

<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:

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:

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.

Zobacz też