Prueba del middleware de ASP.NET Core

Por Chris Ross

El middleware se puede probar en aislamiento con TestServer. Eso le permite lo siguiente:

  • Crear una instancia de la canalización de una aplicación que contenga solo los componentes que se necesiten probar.
  • Enviar solicitudes personalizadas para comprobar el comportamiento del middleware.

Ventajas:

  • Las solicitudes se envían en memoria en lugar de serializarse a través de la red.
  • Esto evita problemas adicionales, como la administración de puertos y los certificados HTTPS.
  • Las excepciones del middleware pueden fluir directamente de vuelta a la prueba que llama.
  • Es posible personalizar estructuras de datos del servidor, como HttpContext, directamente en la prueba.

Configuración de TestServer

Cree una prueba en el proyecto de prueba:

  • Compile e inicie un host que use TestServer.

  • Agregue los servicios necesarios que usa el middleware.

  • Agregue una referencia de paquete al proyecto para el paquete de NuGet Microsoft.AspNetCore.TestHost.

  • Configure la canalización de procesamiento para usar el middleware para la prueba.

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

Nota

Para obtener instrucciones sobre cómo agregar paquetes a aplicaciones .NET, consulte los artículos de Instalación y administración de paquetes en Flujo de trabajo de consumo de paquetes (NuGet documentación). Confirme las versiones correctas del paquete en NuGet.org.

Envío de solicitudes con HttpClient

Envíe una solicitud mediante 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("/");

    ...
}

Declare el resultado. En primer lugar, realice una aserción opuesta al resultado que espera. Una ejecución inicial con una aserción de falso positivo confirma que se produce un error en la prueba cuando el middleware funciona correctamente. Ejecute la prueba y confirme que se produce un error en ella.

En el ejemplo siguiente, el middleware debe devolver un código de estado 404 (No encontrado) cuando se solicita el punto de conexión raíz. Realice la primera serie de pruebas con Assert.NotEqual( ... );, lo que debería generar un error:

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

Cambie la aserción para probar el middleware en condiciones de funcionamiento normales. La prueba final usa Assert.Equal( ... );. Vuelva a ejecutar la prueba para confirmar que se realiza correctamente.

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

Envío de solicitudes con HttpContext

Una aplicación de prueba también puede enviar una solicitud mediante SendAsync(Action<HttpContext>, CancellationToken). En el siguiente ejemplo, se realizan varias comprobaciones cuando el middleware procesa https://example.com/A/Path/?and=query:

[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 permite la configuración directa de un objeto HttpContext en lugar de usar las abstracciones de HttpClient. Use SendAsync para manipular estructuras que solo están disponibles en el servidor, como HttpContext.Items o HttpContext.Features.

Como en el ejemplo anterior, en el que se intentaba obtener una respuesta 404 - No encontrado, compruebe lo contrario a cada instrucción Assert de la prueba anterior. La comprobación confirma que se produce un error en la prueba cuando el middleware funciona con normalidad. Una vez que haya confirmado que la prueba de falso positivo funciona, establezca las instrucciones Assert finales para las condiciones y los valores esperados de la prueba. Vuelva a ejecutarla para confirmar que se realiza correctamente.

Adición de rutas de solicitud

Se pueden agregar rutas adicionales mediante la configuración con la prueba 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);

También se pueden agregar rutas adicionales mediante el enfoque server.SendAsync.

Limitaciones de TestServer

TestServer:

  • se creó para replicar los comportamientos del servidor a fin de probar el middleware.
  • No intenta replicar todos los comportamientos de HttpClient.
  • Trata de proporcionar al cliente acceso a tanto control sobre el servidor como sea posible y con tanta visibilidad como sea posible sobre lo que sucede en el servidor. Por ejemplo, puede producir excepciones no iniciadas normalmente por HttpClient para comunicar directamente el estado del servidor.
  • No establece de forma predeterminada algunos encabezados específicos de transporte, ya que normalmente no son pertinentes para el middleware. Para obtener más información, vea la siguiente sección.
  • Omite la posición Stream pasada a través de StreamContent. HttpClient envía toda la secuencia desde la posición inicial, incluso cuando se establece el posicionamiento. Para más información, consulte este problema de GitHub.

Encabezados Content-Length and Transfer-Encoding

TestServer no establece encabezados de solicitud o respuesta relacionados con el transporte, como Content-Length o Transfer-Encoding. Las aplicaciones deben evitar depender de estos encabezados porque su uso varía según el cliente, el escenario y el protocolo. Si Content-Length y Transfer-Encoding son necesarios para probar un escenario concreto, se pueden especificar en la prueba al crear HttpRequestMessage o HttpContext. Para más información, consulte los siguientes problemas de GitHub: