Integratives Testen mit ASP.NET Core 3.0 und Autofac

Integratives Testen mit ASP.NET Core 3.0 und Autofac

Automatisches Testen ist in vielen Fällen der Weg zum Erfolg eines Software Projektes. Zum einen sind Tests ein wichtiger Teil der Dokumentation, da sie beschreiben wie die Software Artefakte zu verwenden sind und zum anderen werden Fehler früher und somit kostengünstiger aufgedeckt.

ASP.NET Core bietet die TestServer Klasse, welches es ermöglicht ASP.NET Core zu testen ohne das ein Port-Binding erforderlich wird. Das hat u.a. die Vorteile das kein freier Port auf dem Host gesucht werden muss und das es nicht notwendig ist für Server und Client eigene Applikationen zu starten. Mit ASP.NET Core 3.0 haben sich die Schritte geändert um Dependency Injection-Frameworks wie Autofac zu nutzen, dazu gibt es im Internet viele Anleitungen was man Anpassen muss beim Hochrüsten von ASP.NET Core 2.2 zu 3.0. In diesem Blogpost soll der Schwerpunkt darauf liegen wie man den TestServer nutzen kann und auch DependencyInjection nutzen kann um Tests-Stubs/Mocks zu liefern.

Als erstes implementieren wir einen Dekorierier (engl. Decorator), welcher als Konstruktor-Parameter eine Action übergeben werden kann. Diese Action gibt Zugriff auf den ContainerBuilder nachdem alle “Bindings” normal geladen wurden.

/// <summary>
/// Class to decorate Autofac standard ServiceProviderFactory to allow
/// modification of ContainerBuilder for testing
/// </summary>
/// <typeparam name="ContainerBuilder">Generic Type for ContainerBuilder
/// in this case specific for Autofac</typeparam>
internal class AutofacServiceProviderFactoryTestDecorator<ContainerBuilder>
    : IServiceProviderFactory<ContainerBuilder> 
    where ContainerBuilder : Autofac.ContainerBuilder
{
    private readonly AutofacServiceProviderFactory _serviceProviderFactory;
    private readonly Action<Autofac.ContainerBuilder> _modifyContainerCallback;

    /// <summary>
    /// Constructor for ServiceProviderFactory test decorator
    /// </summary>
    /// <param name="modifyContainerCallback">Optional callback to modify
    /// Autofac registrations</param>
    public AutofacServiceProviderFactoryTestDecorator(
        Action<Autofac.ContainerBuilder> modifyContainerCallback = null)
    {
        _serviceProviderFactory = new AutofacServiceProviderFactory();
        _modifyContainerCallback = modifyContainerCallback;
    }

    /// <inheritdoc />
    public ContainerBuilder CreateBuilder(IServiceCollection services)
    {
        return _serviceProviderFactory.CreateBuilder(services)
            as ContainerBuilder;
    }

    /// <inheritdoc />
    public IServiceProvider CreateServiceProvider(
        ContainerBuilder containerBuilder)
    {
        // allow replacement of registered types
        _modifyContainerCallback?.Invoke(containerBuilder);
        return _serviceProviderFactory.CreateServiceProvider(
            containerBuilder);
    }
}

Als zweiten Schritt wird eine Hilfesmethode erstellt, deren Aufgabe es ist den Host zu konfigurieren und den Test-Deocorator zu nutzen.

/// <summary>
/// Using Hostbuilder and Startup to create TestServer (no real Port Binding required)
/// </summary>
/// <param name="modifyContainerBuilderCallback">callback provid access to Autofac.ContainerBuilder and allow you to replace registrations with test stubs/mocks, just reregister the type</param>
/// <returns>Task to start the host async </returns>
/// <remarks>DO NOT forget to stop and dispose the host after use</remarks>
internal static Task<IHost> GetTestServer(Action<ContainerBuilder> modifyContainerBuilderCallback = null)
{
    var hostBuilder = new HostBuilder()
    .UseServiceProviderFactory(
        new AutofacServiceProviderFactoryTestDecorator<ContainerBuilder>(modifyContainerBuilderCallback))
    .ConfigureWebHost(webHost =>
    {
        webHost
        .UseTestServer()
        .UseStartup<Startup>();
    })
    .UseSerilog()
    .UseEnvironment("Test");

    return hostBuilder.StartAsync();
}

Als letzter Schritt, kann man die Hilfsmethode aus einem Test nutzen und ein bestehendes Binding durch Stubs/Mocks ersetzen.

[Test]
public async Task Test_RootRoute_Expect_RouteExist()
{
     using (var host = await TestHelper.GetTestServer(containerBuilder =>
     {
     containerBuilder.RegisterType<MyTestStub().AsImplementedInterfaces();
    }))
     {
        using (var client = host.GetTestClient())
        using (var response = await client.GetAsync(/))
         {
             response.EnsureSuccessStatusCode();
         }
         await host.StopAsync();
     }
}

Sollte es zu einem Compile-Fehler kommen, dass die Methode RegisterType nicht gefunden werden kann, muss der Namespace Autofac hinzugefügt werden um Zugriff auf die Erweiterungsmethoden (engl. extension method) zu erhalten.

Die Kommentare sind geschlossen.