測試 ASP.NET Core 中介軟體

作者:Chris Ross

中介軟體可使用 TestServer 以隔離方式進行測試。 這可讓您:

  • 具現化只包含您需測試元件的應用程式管線。
  • 傳送自訂要求以驗證中介軟體行為。

優點:

  • 要求會在記憶體內部傳送,而不是透過網路序列化。
  • 這可避免其他疑慮,例如連結埠管理和 HTTPS 憑證。
  • 中介軟體中的例外狀況可直接流回呼叫測試。
  • 您可以直接在測試中自訂伺服器資料結構,例如 HttpContext

設定 TestServer

在測試專案中,建立測試:

  • 建置並啟動使用 TestServer 的主機。

  • 新增中介軟體使用的任何必要服務。

  • Microsoft.AspNetCore.TestHost NuGet 套件的專案新增套件參考。

  • 設定處理管線以使用中介軟體進行測試。

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

注意

如需將套件新增至 .NET 應用程式的指引,請參閱在套件取用工作流程 (NuGet 文件)安裝及管理套件底下的文章。 在 NuGet.org 確認正確的套件版本。

使用 HttpClient 傳送要求

使用 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("/");

    ...
}

判斷提示結果。 首先,做出與您期望的結果相反的判斷提示。 具有誤判為真判斷提示的初始執行會確認當中介軟體正確執行時,測試失敗。 執行測試並確認測試失敗。

在下列範例中,當要求根端點時,中介軟體應該傳回 404 狀態碼 (找不到)。 使用 Assert.NotEqual( ... ); 進行第一個測試回合,這應該會失敗:

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

變更判斷提示,以在正常作業條件下測試中介軟體。 最終測試會使用 Assert.Equal( ... );。 再次執行測試以確認通過測試。

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

使用 HttpContext 傳送要求

測試應用程式也可以使用 SendAsync(Action<HttpContext>, CancellationToken) 傳送要求。 在下列範例中,由中介軟體處理 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 允許直接設定 HttpContext 物件,而不使用 HttpClient 抽象概念。 使用 SendAsync 來操作僅在伺服器上可用的結構,例如 HttpCoNtext.ItemsHttpCoNtext.Features

如同先前針對「404 - 找不到」回應測試的範例,請檢查上述測試中每個 Assert 陳述式的相反項目。 此檢查可確認當中間件正常運作時,測試正確失敗。 確認誤判為真測試可運作之後,請針對測試的預期條件和值設定最終 Assert 陳述式。 再次執行,以確認通過測試。

新增要求路由

您可以使用測試 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);

您也可以使用 方法 server.SendAsync新增其他路由。

TestServer 限制

TestServer:

  • 已建立來複寫伺服器行為,以測試中介軟體。
  • 請勿嘗試複寫所有的 HttpClient 行為。
  • 嘗試讓用戶端得以盡可能控制伺服器,而且盡可能得知伺服器上發生的情況。 例如,其可能擲回 HttpClient 通常不會擲回的例外狀況,以便直接傳達伺服器狀態。
  • 預設不要設定某些傳輸特定標頭,因為這些標頭通常與中介軟體無關。 如需詳細資訊,請參閱一節。
  • 忽略透過 StreamContent 傳遞的 Stream 位置。 HttpClient 會從開始位置傳送整個串流,即使已設定定位也一樣。 如需詳細資訊,請參閱這個 GitHub 問題。

Content-Length 和 Transfer-Encoding 標頭

TestServer 不會設定傳輸相關要求或回應標頭,例如 Content-LengthTransfer-Encoding。 應用程式應避免取決於這些標頭,因為其使用方式因用戶端、案例和通訊協定而異。 如果需要 Content-LengthTransfer-Encoding 來測試特定案例,可在撰寫 HttpRequestMessageHttpContext 時,在測試中加以指定。 如需詳細資訊,請參閱下列 GitHub 問題: