Compartir a través de


Administrar el host de la aplicación en pruebas de .NET.NET Aspire

Al escribir pruebas funcionales o de integración con .NET.NET Aspire, es fundamental gestionar de manera eficaz la instancia del host de la aplicación . El host de la aplicación representa el entorno de aplicación completo y puede ser costoso crear y anular. En este artículo se explica cómo administrar la instancia de host de la aplicación en las pruebas de .NET.NET Aspire.

Para escribir pruebas con .NET.NET Aspire, se usa el paquete nuGet de 📦 Aspire.Hosting.Testing que contiene algunas clases auxiliares para administrar la instancia de host de la aplicación en las pruebas.

Uso de la clase DistributedApplicationTestingBuilder

En el tutorial sobre cómo escribir tu primera prueba, se le presentó la clase DistributedApplicationTestingBuilder que se puede usar para crear una instancia de host de la aplicación.

var appHost = await DistributedApplicationTestingBuilder
    .CreateAsync<Projects.AspireApp_AppHost>();

El método DistributedApplicationTestingBuilder.CreateAsync<T> toma el tipo de proyecto host de la aplicación como parámetro genérico para crear la instancia de host de la aplicación. Aunque este método se ejecuta al principio de cada prueba, es más eficaz crear la instancia de host de la aplicación una vez y compartirla entre pruebas a medida que crece el conjunto de pruebas.

Con xUnit, se implementa la interfaz IAsyncLifetime en la clase de prueba para admitir la inicialización asincrónica y la eliminación de la instancia de host de la aplicación. El método InitializeAsync se usa para crear la instancia de host de la aplicación antes de ejecutar las pruebas y el método DisposeAsync elimina el host de la aplicación una vez completadas las pruebas.

public class WebTests : IAsyncLifetime
{
    private DistributedApplication _app;

    public async Task InitializeAsync()
    {
        var appHost = await DistributedApplicationTestingBuilder
            .CreateAsync<Projects.AspireApp_AppHost>();

        _app = await appHost.BuildAsync();
    }

    public async Task DisposeAsync() => await _app.DisposeAsync();

    [Fact]
    public async Task GetWebResourceRootReturnsOkStatusCode()
    {
        // test code here
    }
}

Con MSTest, se usan los ClassInitializeAttribute y ClassCleanupAttribute en métodos estáticos de la clase de prueba para proporcionar la inicialización y limpieza de la instancia de host de la aplicación. El método ClassInitialize se usa para crear la instancia de host de la aplicación antes de ejecutar las pruebas y el método ClassCleanup elimina la instancia de host de la aplicación una vez completadas las pruebas.

[TestClass]
public class WebTests
{
    private static DistributedApplication _app;

    [ClassInitialize]
    public static async Task ClassInitialize(TestContext context)
    {
       var appHost = await DistributedApplicationTestingBuilder
            .CreateAsync<Projects.AspireApp_AppHost>();

        _app = await appHost.BuildAsync();
    }

    [ClassCleanup]
    public static async Task ClassCleanup() => await _app.DisposeAsync();

    [TestMethod]
    public async Task GetWebResourceRootReturnsOkStatusCode()
    {
        // test code here
    }
}

Con NUnit, se usan los atributos OneTimeSetUp y OneTimeTearDown en métodos de la clase de prueba para proporcionar la configuración y desmontaje de la instancia de host de la aplicación. El método OneTimeSetUp se usa para crear la instancia de host de la aplicación antes de ejecutar las pruebas y el método OneTimeTearDown elimina la instancia de host de la aplicación una vez completadas las pruebas.

public class WebTests
{
    private DistributedApplication _app;

    [OneTimeSetUp]
    public async Task OneTimeSetup()
    {
       var appHost = await DistributedApplicationTestingBuilder
            .CreateAsync<Projects.AspireApp_AppHost>();

        _app = await appHost.BuildAsync();
    }

    [OneTimeTearDown]
    public async Task OneTimeTearDown() => await _app.DisposeAsync();

    [Test]
    public async Task GetWebResourceRootReturnsOkStatusCode()
    {
        // test code here
    }
}

Al capturar el host de la aplicación en un campo cuando se inicia la ejecución de pruebas, puede acceder a él en cada prueba sin necesidad de volver a crearla, lo que reduce el tiempo necesario para ejecutar las pruebas. A continuación, cuando se completa la ejecución de pruebas, se elimina el host de la aplicación, que limpia los recursos que se crearon durante la ejecución de pruebas, como los contenedores.

Pasar argumentos al anfitrión de la aplicación

Puede acceder a los argumentos desde el host de la aplicación con el parámetro args. Los argumentos también se pasan al sistema de configuración de .NET, por lo que puede sobrescribir muchas opciones de configuración de esta manera. En el ejemplo siguiente, invalida el entorno de especificando como opción de línea de comandos:

var builder = await DistributedApplicationTestingBuilder
    .CreateAsync<Projects.MyAppHost>(
    [
        "--environment=Testing"
    ]);

Otros argumentos se pueden pasar al host de la aplicación Program y estar disponibles en el host de la aplicación. En el ejemplo siguiente, se pasa un argumento al host de la aplicación y se usa para controlar si agrega volúmenes de datos a una instancia de Postgres.

En el host de la aplicación Program, se usa la configuración para admitir la habilitación o deshabilitación de volúmenes:

var postgres = builder.AddPostgres("postgres1");
if (builder.Configuration.GetValue("UseVolumes", true))
{
    postgres.WithDataVolume();
}

En el código de prueba, pasas "UseVolumes=false" en el args al anfitrión de la aplicación.

public async Task DisableVolumesFromTest()
{
    // Disable volumes in the test builder via arguments:
    using var builder = await DistributedApplicationTestingBuilder
        .CreateAsync<Projects.TestingAppHost1_AppHost>(
        [
            "UseVolumes=false"
        ]);

    // The container will have no volume annotation since we disabled volumes by passing UseVolumes=false
    var postgres = builder.Resources.Single(r => r.Name == "postgres1");

    Assert.Empty(postgres.Annotations.OfType<ContainerMountAnnotation>());
}

Uso de la clase DistributedApplicationFactory

Aunque la clase DistributedApplicationTestingBuilder es útil para muchos escenarios, puede haber situaciones en las que quiera tener más control sobre cómo iniciar el host de la aplicación, como ejecutar código antes de crear el generador o después de compilar el host de la aplicación. En estos casos, implementa tu propia versión de la clase DistributedApplicationFactory. Esto es lo que usa el DistributedApplicationTestingBuilder internamente.

public class TestingAspireAppHost()
    : DistributedApplicationFactory(typeof(Projects.AspireApp_AppHost))
{
    // override methods here
}

El constructor requiere el tipo de referencia del proyecto host de la aplicación como parámetro. Opcionalmente, puede proporcionar argumentos al generador de aplicaciones host subyacente. Estos argumentos controlan cómo se inicia el host de la aplicación y proporcionan valores a la variable args usada por el archivo Program.cs para iniciar la instancia de host de la aplicación.

Métodos de ciclo de vida

La clase DistributionApplicationFactory proporciona varios métodos de ciclo de vida que se pueden invalidar para proporcionar un comportamiento personalizado a lo largo de la preparación y creación del host de la aplicación. Los métodos disponibles son OnBuilderCreating, OnBuilderCreated, OnBuildingy OnBuilt.

Por ejemplo, podemos usar el método OnBuilderCreating para establecer la configuración, como la información de suscripción y grupo de recursos para Azure, antes de que se cree el host de la aplicación y se aprovisionan los recursos de Azure dependientes, lo que da lugar a que nuestras pruebas usen el entorno de Azure correcto.

public class TestingAspireAppHost() : DistributedApplicationFactory(typeof(Projects.AspireApp_AppHost))
{
    protected override void OnBuilderCreating(DistributedApplicationOptions applicationOptions, HostApplicationBuilderSettings hostOptions)
    {
        hostOptions.Configuration ??= new();
        hostOptions.Configuration["environment"] = "Development";
        hostOptions.Configuration["AZURE_SUBSCRIPTION_ID"] = "00000000-0000-0000-0000-000000000000";
        hostOptions.Configuration["AZURE_RESOURCE_GROUP"] = "my-resource-group";
    }
}

Debido al orden de prioridad en el sistema de configuración de .NET, las variables de entorno se usarán sobre cualquier elemento del archivo appsettings.json o secrets.json.

Otro escenario que puede usar en el ciclo de vida es configurar los servicios usados por el host de la aplicación. En el ejemplo siguiente, considere un escenario en el que sobrescribe la API de OnBuilderCreated para agregar resiliencia a HttpClient:

protected override void OnBuilderCreated(
    DistributedApplicationBuilder applicationBuilder)
{
    applicationBuilder.Services.ConfigureHttpClientDefaults(clientBuilder =>
    {
        clientBuilder.AddStandardResilienceHandler();
    });
}

Consulte también