Testen von ASP.NET Core-MiddlewareTest ASP.NET Core middleware

Von Chris RossBy Chris Ross

Middleware kann mit TestServer isoliert getestet werden.Middleware can be tested in isolation with TestServer. Die Funktion ermöglicht Folgendes:It allows you to:

  • Instanziieren Sie eine App-Pipeline, die nur die Komponenten enthält, die Sie testen müssen.Instantiate an app pipeline containing only the components that you need to test.
  • Senden Sie benutzerdefinierte Anforderungen, um das Verhalten der Middleware zu überprüfen.Send custom requests to verify middleware behavior.

Vorteile:Advantages:

  • Anforderungen werden arbeitsspeicherintern gesendet und nicht über das Netzwerk serialisiert.Requests are sent in-memory rather than being serialized over the network.
  • Dadurch werden zusätzliche Aspekte wie Portverwaltung und HTTPS-Zertifikate vermieden.This avoids additional concerns, such as port management and HTTPS certificates.
  • Ausnahmen in der Middleware können direkt an den aufrufenden Test zurückfließen.Exceptions in the middleware can flow directly back to the calling test.
  • Es ist möglich, Serverdatenstrukturen, wie z. B. HttpContext, direkt im Test anzupassen.It's possible to customize server data structures, such as HttpContext, directly in the test.

Einrichten von TestServerSet up the TestServer

Erstellen Sie im Testprojekt einen Test:In the test project, create a test:

  • Erstellen und starten Sie einen Host, der TestServer verwendet.Build and start a host that uses TestServer.

  • Fügen Sie alle benötigten Dienste hinzu, die die Middleware verwendet.Add any required services that the middleware uses.

  • Fügen dem Projekt das NuGet-Paket Microsoft.AspNetCore.TestHost hinzu:Add the Microsoft.AspNetCore.TestHost NuGet package to the project:

    <ItemGroup>
      <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="3.1.*" />
    </ItemGroup>
    
  • Konfigurieren Sie die Verarbeitungspipeline für die Nutzung der Middleware für den Test.Configure the processing pipeline to use the middleware for the test.

[Fact]
public async Task MiddlewareTest_ReturnsNotFoundForRequest()
{
    using var host = await new HostBuilder()
        .ConfigureWebHost(webBuilder =>
        {
            webBuilder
                .UseTestServer()
                .ConfigureServices(services =>
                {
                    services.AddMyServices();
                })
                .Configure(app =>
                {
                    app.UseMiddleware<MyMiddleware>();
                });
        })
        .StartAsync();

    ...
}

Senden von Anforderungen mit HttpClientSend requests with HttpClient

Senden Sie eine Anforderung mit HttpClient:Send a request using HttpClient:

[Fact]
public async Task MiddlewareTest_ReturnsNotFoundForRequest()
{
    using var host = await new HostBuilder()
        .ConfigureWebHost(webBuilder =>
        {
            webBuilder
                .UseTestServer()
                .ConfigureServices(services =>
                {
                    services.AddMyServices();
                })
                .Configure(app =>
                {
                    app.UseMiddleware<MyMiddleware>();
                });
        })
        .StartAsync();

    var response = await host.GetTestServer().CreateClient().GetAsync("/");

    ...
}

Bestätigen Sie das Ergebnis.Assert the result. Machen Sie zunächst eine Assertion, die das Gegenteil des von Ihnen erwarteten Ergebnisses darstellt.First, make an assertion the opposite of the result that you expect. Ein erster Lauf mit einer falsch-positiven Assertion bestätigt, dass der Test fehlschlägt, wenn die Middleware einwandfrei funktioniert.An initial run with a false positive assertion confirms that the test fails when the middleware is performing correctly. Führen Sie den Test aus, und bestätigen Sie, dass der Test fehlschlägt.Run the test and confirm that the test fails.

Im folgenden Beispiel sollte die Middleware den Statuscode 404 (Nicht gefunden) zurückgeben, wenn der Stammendpunkt angefordert wird.In the following example, the middleware should return a 404 status code (Not Found) when the root endpoint is requested. Führen Sie den ersten Testlauf mit Assert.NotEqual( ... ); durch, der fehlschlagen sollte:Make the first test run with Assert.NotEqual( ... );, which should fail:

[Fact]
public async Task MiddlewareTest_ReturnsNotFoundForRequest()
{
    using var host = await new HostBuilder()
        .ConfigureWebHost(webBuilder =>
        {
            webBuilder
                .UseTestServer()
                .ConfigureServices(services =>
                {
                    services.AddMyServices();
                })
                .Configure(app =>
                {
                    app.UseMiddleware<MyMiddleware>();
                });
        })
        .StartAsync();

    var testServer = host.GetTestServer();
    
    var testClient = testServer.CreateClient();
    var response = await testClient.GetAsync("/");

    Assert.NotEqual(HttpStatusCode.NotFound, response.StatusCode);
}

Ändern Sie die Assertion so, dass die Middleware unter normalen Betriebsbedingungen getestet wird.Change the assertion to test the middleware under normal operating conditions. Im letzten Test wird Assert.Equal( ... ); verwendet.The final test uses Assert.Equal( ... );. Führen Sie den Test erneut aus, um zu bestätigen, dass er bestanden wird.Run the test again to confirm that it passes.

[Fact]
public async Task MiddlewareTest_ReturnsNotFoundForRequest()
{
    using var host = await new HostBuilder()
        .ConfigureWebHost(webBuilder =>
        {
            webBuilder
                .UseTestServer()
                .ConfigureServices(services =>
                {
                    services.AddMyServices();
                })
                .Configure(app =>
                {
                    app.UseMiddleware<MyMiddleware>();
                });
        })
        .StartAsync();

    var response = await host.GetTestServer().CreateClient().GetAsync("/");

    Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}

Senden von Anforderungen mit HttpContextSend requests with HttpContext

Eine Test-App kann eine Anforderung auch mit SendAsync(Action<HttpContext>, CancellationToken) senden.A test app can also send a request using SendAsync(Action<HttpContext>, CancellationToken). Im folgenden Beispiel erfolgen mehrere Prüfungen, wenn https://example.com/A/Path/?and=query von der Middleware verarbeitet wird:In the following example, several checks are made when https://example.com/A/Path/?and=query is processed by the middleware:

[Fact]
public async Task TestMiddleware_ExpectedResponse()
{
    using var host = await new HostBuilder()
        .ConfigureWebHost(webBuilder =>
        {
            webBuilder
                .UseTestServer()
                .ConfigureServices(services =>
                {
                    services.AddMyServices();
                })
                .Configure(app =>
                {
                    app.UseMiddleware<MyMiddleware>();
                });
        })
        .StartAsync();

    var server = host.GetTestServer();
    server.BaseAddress = new Uri("https://example.com/A/Path/");

    var context = await server.SendAsync(c =>
    {
        c.Request.Method = HttpMethods.Post;
        c.Request.Path = "/and/file.txt";
        c.Request.QueryString = new QueryString("?and=query");
    });

    Assert.True(context.RequestAborted.CanBeCanceled);
    Assert.Equal(HttpProtocol.Http11, context.Request.Protocol);
    Assert.Equal("POST", context.Request.Method);
    Assert.Equal("https", context.Request.Scheme);
    Assert.Equal("example.com", context.Request.Host.Value);
    Assert.Equal("/A/Path", context.Request.PathBase.Value);
    Assert.Equal("/and/file.txt", context.Request.Path.Value);
    Assert.Equal("?and=query", context.Request.QueryString.Value);
    Assert.NotNull(context.Request.Body);
    Assert.NotNull(context.Request.Headers);
    Assert.NotNull(context.Response.Headers);
    Assert.NotNull(context.Response.Body);
    Assert.Equal(404, context.Response.StatusCode);
    Assert.Null(context.Features.Get<IHttpResponseFeature>().ReasonPhrase);
}

SendAsync erlaubt die Direktkonfiguration eines HttpContext-Objekts, anstatt die HttpClient-Abstraktionen zu verwenden.SendAsync permits direct configuration of an HttpContext object rather than using the HttpClient abstractions. Verwenden Sie SendAsync, um Strukturen zu manipulieren, die nur auf dem Server verfügbar sind, wie z. B. HttpContext.Items oder HttpContext.Features.Use SendAsync to manipulate structures only available on the server, such as HttpContext.Items or HttpContext.Features.

Wie im früheren Beispiel, das auf eine Antwort des Typs 404 – nicht gefunden getestet wurde, überprüfen Sie das Gegenteil für jede Assert-Anweisung im vorhergehenden Test.As with the earlier example that tested for a 404 - Not Found response, check the opposite for each Assert statement in the preceding test. Die Überprüfung bestätigt, dass der Test bei normalem Betrieb der Middleware ordnungsgemäß fehlschlägt.The check confirms that the test fails correctly when the middleware is operating normally. Nachdem Sie bestätigt haben, dass der falsch positive Test funktioniert, legen Sie die endgültigen Assert-Anweisungen für die erwarteten Bedingungen und Werte des Tests fest.After you've confirmed that the false positive test works, set the final Assert statements for the expected conditions and values of the test. Führen Sie ihn erneut aus, um zu bestätigen, dass er bestanden wird.Run it again to confirm that the test passes.

TestServer-EinschränkungenTestServer limitations

TestServer:TestServer:

  • Wurde erstellt, um Serververhalten zum Testen von Middleware zu replizieren.Was created to replicate server behaviors to test middleware.
  • Versucht nicht, alle HttpClient-Verhalten zu replizieren.Does not try to replicate all HttpClient behaviors.
  • Es wird versucht, dem Client so viel Kontrolle über den Server wie möglich zu geben, mit weitestgehendem Einblick in die Vorgänge auf dem Server.Attempts to give the client access to as much control over the server as possible, and with as much visibility into what's happening on the server as possible. Beispielsweise können Ausnahmen ausgelöst werden, die normalerweise nicht von HttpClient ausgelöst werden, um den Serverzustand direkt zu kommunizieren.For example it may throw exceptions not normally thrown by HttpClient in order to directly communicate server state.
  • Standardmäßig werden einige transportspezifische Header nicht festgelegt, da diese in der Regel für Middleware nicht relevant sind.Doesn't set some transport specific headers by default as those are not usually relevant to middleware. Weitere Informationen finden Sie im nächsten Abschnitt.For more information, see the next section.

Content-Length- und Transfer-Encoding-HeaderContent-Length and Transfer-Encoding headers

TestServer legt keine transportbezogenen Anforderungs- oder Antwortheader wie Content-Length oder Transfer-Encoding fest.TestServer does not set transport related request or response headers such as Content-Length or Transfer-Encoding. In Anwendungen sollten Abhängigkeiten von diesen Headern vermieden werden, weil ihre Verwendung je nach Client, Szenario und Protokoll variiert.Applications should avoid depending on these headers because their usage varies by client, scenario, and protocol. Wenn Content-Length und Transfer-Encoding erforderlich sind, um ein bestimmtes Szenario zu testen, können sie im Test angegeben werden, wenn HttpRequestMessage oder HttpContext verfasst wird.If Content-Length and Transfer-Encoding are necessary to test a specific scenario, they can be specified in the test when composing the HttpRequestMessage or HttpContext. Weitere Informationen finden Sie in den folgenden GitHub-Issues:For more information, see the following GitHub issues: