Partager via


Gérer l’hôte d’application dans .NET.NET Aspire tests

Lors de l’écriture de tests fonctionnels ou d’intégration avec .NET.NET Aspire, la gestion efficace de l'instance de l’hôte d’application est essentielle. L’hôte de l’application représente l’environnement d’application complet et peut être coûteux pour créer et détruire. Cet article explique comment gérer l’instance hôte de l’application dans vos tests de .NET.NET Aspire.

Pour écrire des tests avec .NET.NET Aspire, vous utilisez le package NuGet 📦 Aspire.Hosting.Testing qui contient certaines classes d’assistance pour gérer l’instance hôte de l’application dans vos tests.

Utiliser la classe DistributedApplicationTestingBuilder

Dans le didacticiel sur l’écriture de votre premierde test, vous avez été introduit dans la classe DistributedApplicationTestingBuilder qui peut être utilisée pour créer l’instance hôte de l’application :

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

La méthode DistributedApplicationTestingBuilder.CreateAsync<T> prend le type de projet hôte d’application comme paramètre générique pour créer l’instance hôte de l’application. Bien que cette méthode soit exécutée au début de chaque test, il est plus efficace de créer l’instance hôte de l’application une fois et de la partager entre les tests à mesure que la suite de tests augmente.

Avec xUnit, vous implémentez l’interface IAsyncLifetime sur la classe de test pour prendre en charge l’initialisation et la suppression asynchrones de l’instance hôte de l’application. La méthode InitializeAsync est utilisée pour créer l’instance hôte de l’application avant l’exécution des tests et la méthode DisposeAsync supprime l’hôte de l’application une fois les tests terminés.

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

Avec MSTest, vous utilisez les ClassInitializeAttribute et les ClassCleanupAttribute sur les méthodes statiques de la classe de test pour fournir l’initialisation et le nettoyage de l’instance hôte de l’application. La méthode ClassInitialize est utilisée pour créer l’instance hôte de l’application avant l’exécution des tests et la méthode ClassCleanup supprime l’instance hôte de l’application une fois les tests terminés.

[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
    }
}

Avec NUnit, vous utilisez les attributs OneTimeSetUp et OneTimeTearDown sur les méthodes de la classe de test pour fournir la configuration et la suppression de l’instance hôte de l’application. La méthode OneTimeSetUp est utilisée pour créer l’instance hôte de l’application avant l’exécution des tests et la méthode OneTimeTearDown supprime l’instance hôte de l’application une fois les tests terminés.

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

En capturant l’hôte d’application dans un champ au démarrage de l’exécution du test, vous pouvez y accéder dans chaque test sans avoir à le recréer, ce qui réduit le temps nécessaire pour exécuter les tests. Ensuite, une fois l’exécution de test terminée, l’hôte de l’application est supprimé, ce qui nettoie toutes les ressources créées pendant l’exécution de test, telles que les conteneurs.

Transmettre des arguments à votre hôte d’application

Vous pouvez accéder aux arguments de votre hôte d’application avec le paramètre args. Les arguments sont également passés au système de configuration de .NET, vous permettant ainsi de remplacer de nombreux paramètres de configuration. Dans l’exemple suivant, vous remplacez l’environnement en le spécifiant comme option de ligne de commande :

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

D’autres arguments peuvent être passés à votre hôte d’application Program et mis à disposition dans votre hôte d’application. Dans l’exemple suivant, vous passez un argument à l’hôte de l’application et utilisez-le pour contrôler si vous ajoutez des volumes de données à une instance de Postgres.

Dans l’hôte d’application Program, vous utilisez la configuration pour prendre en charge l’activation ou la désactivation des volumes :

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

Dans le code de test, vous passez "UseVolumes=false" dans l'args à l’hôte de l’application :

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

Utiliser la classe DistributedApplicationFactory

Bien que la classe DistributedApplicationTestingBuilder soit utile pour de nombreux scénarios, il peut y avoir des situations où vous souhaitez plus de contrôle sur le démarrage de l’hôte d’application, par exemple l’exécution du code avant la création du générateur ou après la génération de l’hôte de l’application. Dans ce cas, vous implémentez votre propre version de la classe DistributedApplicationFactory. C’est ce que le DistributedApplicationTestingBuilder utilise en interne.

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

Le constructeur nécessite le type de référence du projet hôte d’application en tant que paramètre. Si vous le souhaitez, vous pouvez fournir des arguments au générateur d’application hôte sous-jacent. Ces arguments contrôlent le démarrage de l’hôte d’application et fournissent des valeurs à la variable args utilisée par le fichier Program.cs pour démarrer l’instance hôte de l’application.

Méthodes de cycle de vie

La classe DistributionApplicationFactory fournit plusieurs méthodes de cycle de vie qui peuvent être substituées pour fournir un comportement personnalisé tout au long de la préparation et de la création de l’hôte d’application. Les méthodes disponibles sont OnBuilderCreating, OnBuilderCreated, OnBuildinget OnBuilt.

Par exemple, nous pouvons utiliser la méthode OnBuilderCreating pour définir la configuration, telle que les informations d’abonnement et de groupe de ressources pour Azure, avant la création de l’hôte de l’application et l’approvisionnement de toutes les ressources Azure dépendantes, ce qui entraîne l’utilisation de l’environnement de Azure approprié.

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

En raison de l’ordre de priorité dans le système de configuration .NET, les variables d’environnement seront utilisées sur tout élément du fichier appsettings.json ou secrets.json.

Un autre scénario que vous souhaiterez peut-être utiliser dans le cycle de vie consiste à configurer les services utilisés par l’hôte de l’application. Dans l’exemple suivant, envisagez un scénario dans lequel vous remplacez l’API OnBuilderCreated pour ajouter la résilience au HttpClient:

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

Voir aussi