Gestisci l'host dell'app nei test di .NET.NET Aspire
Quando si scrivono test funzionali o di integrazione con .NET.NET Aspire, gestire in modo efficiente l'istanza dell'host dell'app è fondamentale. L'host dell'app rappresenta l'intero ambiente applicativo e può essere dispendioso da creare e smantellare. Questo articolo illustra come gestire l'istanza dell'host dell'app nei test .NET.NET Aspire.
Per scrivere test con .NET.NET Aspire, usi il pacchetto NuGet 📦 Aspire.Hosting.Testing
che contiene alcune classi di supporto per gestire l'istanza dell'host dell'app nei tuoi test.
Usare la classe DistributedApplicationTestingBuilder
Nell'esercitazione sulla scrittura del tuo primo test, ti è stata introdotta la classe DistributedApplicationTestingBuilder, che può essere utilizzata per creare l'istanza dell'host dell'app:
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
Il metodo DistributedApplicationTestingBuilder.CreateAsync<T>
accetta il tipo di progetto host dell'app come parametro generico per creare l'istanza host dell'app. Anche se questo metodo viene eseguito all'inizio di ogni test, è più efficiente creare l'istanza host dell'app una sola volta e condividerla tra i test man mano che aumenta la crescita del gruppo di test.
In xUnit, si implementa l'interfaccia IAsyncLifetime nella classe di test per supportare l'inizializzazione asincrona e l'eliminazione dell'istanza dell'host dell'app. Il metodo InitializeAsync
viene usato per creare l'istanza host dell'app prima dell'esecuzione dei test e il metodo DisposeAsync
elimina l'host dell'app al termine dei test.
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, si usano i ClassInitializeAttribute e i ClassCleanupAttribute sui metodi statici della classe di test per eseguire l'inizializzazione e la pulizia dell'istanza dell'host dell'app. Il metodo ClassInitialize
viene usato per creare l'istanza host dell'app prima dell'esecuzione dei test e il metodo ClassCleanup
elimina l'istanza host dell'app al termine dei test.
[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 si usano gli attributi OneTimeSetUp e OneTimeTearDown sui metodi della classe di test per fornire l'impostazione e la rimozione dell'istanza dell'host dell'app. Il metodo OneTimeSetUp
viene usato per creare l'istanza host dell'app prima dell'esecuzione dei test e il metodo OneTimeTearDown
elimina l'istanza host dell'app al termine dei test.
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
}
}
Registrando l'host dell'app in un campo all'inizio dell'esecuzione del test, è possibile accedervi per ogni test senza la necessità di ricrearlo, riducendo il tempo necessario per eseguire i test. Al termine dell'esecuzione del test, l'host dell'app viene eliminato, che pulisce tutte le risorse create durante l'esecuzione del test, ad esempio i contenitori.
Passare argomenti all'host dell'app
È possibile accedere agli argomenti dall'host dell'app con il parametro args
. Gli argomenti vengono passati anche al sistema di configurazione di .NET, in modo da poter eseguire l'override di molte impostazioni di configurazione in questo modo. Nell'esempio seguente si esegue l'override dell'ambiente specificandolo come opzione della riga di comando:
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.MyAppHost>(
[
"--environment=Testing"
]);
Altri argomenti possono essere passati all'host dell'applicazione Program
e resi disponibili nell'host dell'applicazione. Nell'esempio seguente si passa un argomento all'host dell'app e lo si usa per controllare se si aggiungono volumi di dati a un'istanza di Postgres.
Nell'host dell'app Program
, si usa la configurazione per gestire l'abilitazione o disabilitazione dei volumi.
var postgres = builder.AddPostgres("postgres1");
if (builder.Configuration.GetValue("UseVolumes", true))
{
postgres.WithDataVolume();
}
Nel codice di test si passa "UseVolumes=false"
nel args
all'host dell'app:
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>());
}
Usare la classe DistributedApplicationFactory
Anche se la classe DistributedApplicationTestingBuilder
è utile per molti scenari, potrebbero verificarsi situazioni in cui si vuole un maggiore controllo sull'avvio dell'host dell'app, ad esempio l'esecuzione di codice prima che il generatore venga creato o dopo la compilazione dell'host dell'app. In questi casi, si implementa la propria versione della classe DistributedApplicationFactory. Questo è ciò che il DistributedApplicationTestingBuilder
usa internamente.
public class TestingAspireAppHost()
: DistributedApplicationFactory(typeof(Projects.AspireApp_AppHost))
{
// override methods here
}
Il costruttore richiede il tipo di riferimento del progetto host dell'app come parametro. Facoltativamente, è possibile fornire argomenti al generatore di applicazioni host sottostante. Questi argomenti controllano l'avvio dell'host dell'app e forniscono valori alla variabile args usata dal file Program.cs per avviare l'istanza dell'host dell'app.
Metodi relativi al ciclo di vita
La classe DistributionApplicationFactory
fornisce diversi metodi del ciclo di vita che possono essere sottoposti a override per fornire un comportamento personalizzato durante la preparazione e la creazione dell'host dell'app. I metodi disponibili sono OnBuilderCreating
, OnBuilderCreated
, OnBuilding
e OnBuilt
.
Ad esempio, è possibile usare il metodo OnBuilderCreating
per impostare la configurazione, come le informazioni sulla sottoscrizione e sul gruppo di risorse per Azure, prima della creazione dell'host dell'app e del provisioning delle risorse dipendenti Azure, in modo che i nostri test utilizzino l'ambiente Azure corretto.
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";
}
}
A causa dell'ordine di precedenza nel sistema di configurazione .NET, le variabili di ambiente verranno usate su qualsiasi elemento nel file appsettings.json o secrets.json.
Un altro scenario da usare nel ciclo di vita consiste nel configurare i servizi usati dall'host dell'app. Nell'esempio seguente si consideri uno scenario in cui si esegue l'override dell'API OnBuilderCreated
per aggiungere resilienza al HttpClient
:
protected override void OnBuilderCreated(
DistributedApplicationBuilder applicationBuilder)
{
applicationBuilder.Services.ConfigureHttpClientDefaults(clientBuilder =>
{
clientBuilder.AddStandardResilienceHandler();
});
}