Testování middlewaru ASP.NET Core

Autor: Chris Ross

Middleware lze testovat izolovaně s TestServer. Umožňuje:

  • Vytvořte instanci kanálu aplikace obsahující pouze komponenty, které potřebujete testovat.
  • Odeslání vlastních požadavků za účelem ověření chování middlewaru

Výhody:

  • Požadavky se posílají v paměti, nikoli serializovány přes síť.
  • Tím se vyhnete dalším obavám, jako je správa portů a certifikáty HTTPS.
  • Výjimky v middlewaru můžou proudit přímo zpět do volajícího testu.
  • Datové struktury serveru, například HttpContext, je možné přizpůsobit přímo v testu.

Nastavení testovacího serveru

V testovacím projektu vytvořte test:

  • Sestavte a spusťte hostitele, který používá TestServer.

  • Přidejte všechny požadované služby, které middleware používá.

  • Přidejte odkaz na balíček do projektu pro Microsoft.AspNetCore.TestHost balíček NuGet.

  • Nakonfigurujte kanál zpracování tak, aby pro test používal middleware.

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

Poznámka:

Pokyny k přidávání balíčků do aplikací .NET najdete v článcích v části Instalace a správa balíčků na webu Pracovní postup používání balíčků (dokumentace k NuGetu). Ověřte správné verze balíčků na NuGet.org.

Odesílání požadavků pomocí HttpClient

Odeslání žádosti pomocí 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.GetTestClient().GetAsync("/");

    ...
}

Vymažte výsledek. Nejprve proveďte kontrolní výraz, který je opakem očekávaného výsledku. Počáteční spuštění s falešně pozitivním kontrolním výrazem potvrzuje, že test selže, když middleware funguje správně. Spusťte test a ověřte, že test selže.

V následujícím příkladu by middleware měl při vyžádání kořenového koncového bodu vrátit stavový kód 404 (Nenalezený). Proveďte první testovací běh s Assert.NotEqual( ... );chybou, která by měla selhat:

[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.GetTestClient().GetAsync("/");

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

Změňte kontrolní výraz tak, aby testoval middleware za normálních provozních podmínek. Poslední test používá Assert.Equal( ... );. Znovu spusťte test, abyste potvrdili, že projde.

[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.GetTestClient().GetAsync("/");

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

Odesílání požadavků pomocí HttpContextu

Testovací aplikace může také odeslat požadavek pomocí SendAsync(Action<HttpContext>, CancellationToken). V následujícím příkladu se provede několik kontrol při https://example.com/A/Path/?and=query zpracování middlewarem:

[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 umožňuje přímou konfiguraci objektu HttpContext místo použití HttpClient abstrakcí. Slouží SendAsync k manipulaci se strukturami dostupnými pouze na serveru, například HttpContext.Items nebo HttpContext.Features.

Stejně jako v předchozím příkladu, který testoval odpověď 404 – Nenalezena , zkontrolujte opak pro každý Assert příkaz v předchozím testu. Kontrola potvrzuje, že test selže správně, když middleware funguje normálně. Po potvrzení, že falešně pozitivní test funguje, nastavte konečné Assert příkazy pro očekávané podmínky a hodnoty testu. Spusťte ho znovu, abyste potvrdili, že test projde.

Přidání tras žádostí

Další trasy je možné přidat pomocí konfigurace pomocí testu HttpClient:

	[Fact]
	public async Task TestWithEndpoint_ExpectedResponse ()
	{
		using var host = await new HostBuilder()
			.ConfigureWebHost(webBuilder =>
			{
				webBuilder
					.UseTestServer()
					.ConfigureServices(services =>
					{
						services.AddRouting();
					})
					.Configure(app =>
					{
						app.UseRouting();
						app.UseMiddleware<MyMiddleware>();
						app.UseEndpoints(endpoints =>
						{
							endpoints.MapGet("/hello", () =>
								TypedResults.Text("Hello Tests"));
						});
					});
			})
			.StartAsync();

		var client = host.GetTestClient();

		var response = await client.GetAsync("/hello");

		Assert.True(response.IsSuccessStatusCode);
		var responseBody = await response.Content.ReadAsStringAsync();
		Assert.Equal("Hello Tests", responseBody);

Další trasy lze přidat také pomocí přístupu server.SendAsync.

Omezení testovacího serveru

TestServer:

  • Byl vytvořen pro replikaci chování serveru pro testování middlewaru.
  • Nepokouší se replikovat všechna HttpClient chování.
  • Snaží se poskytnout klientovi co nejvíce kontroly nad serverem a co nejvíce přehledně zjistit, co se děje na serveru. Může například vyvolat výjimky, které HttpClient nejsou obvykle vyvolány, aby bylo možné přímo komunikovat stav serveru.
  • Nenastavuje ve výchozím nastavení některé hlavičky specifické pro přenos, protože ty obvykle nejsou pro middleware relevantní. Další informace naleznete v následující části.
  • Přeskočí Stream pozici předanou StreamContent. HttpClient odešle celý datový proud z počáteční pozice, i když je nastaveno umístění. Další informace najdete u tohoto problému na GitHubu.

Hlavičky Content-Length a Transfer-Encoding

TestServer nenastavuje hlavičky požadavku nebo odpovědi související s přenosem, jako je například Content-Length nebo Transfer-Encoding. Aplikace by se měly v závislosti na těchto hlavičkách vyhnout, protože jejich využití se liší podle klienta, scénáře a protokolu. Pokud Content-Length a Transfer-Encoding jsou nezbytné k otestování konkrétního scénáře, je možné je zadat v testu při psaní HttpRequestMessage nebo HttpContext. Další informace najdete v následujících problémech s GitHubem: