共用方式為


測試 ASP.NET 核心中介軟體

作者:Chris Ross

中介軟體可以利用 TestServer 單獨測試。 這可讓您:

  • 實例化一個只包含你需要測試元件的應用程式流程。
  • 發送自訂請求來驗證中介軟體的行為。

優點:

  • 請求是以記憶體方式傳送,而非透過網路序列化。
  • 這避免了額外的問題,例如埠管理和 HTTPS 憑證。
  • 中介軟體中的例外可以直接回溯到呼叫測試。
  • 測試中可以直接自訂伺服器資料結構,例如 HttpContext

設定測試伺服器

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

  • 使用 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 例外,以直接傳達伺服器狀態。
  • 預設不會設定某些傳輸專用標頭,因為那些通常與中介軟體無關。 如需詳細資訊,請參閱下一節。
  • 忽略通過 StreamContentStream 位置。 HttpClient 即使位置已設定,仍會從起始位置傳送整條串流。 如需詳細資訊,請參閱這個 GitHub 問題。

內容長度與 Transfer-Encoding 標頭

TestServer 不會 設定與傳輸相關的請求或回應標頭,例如 內容長度傳輸編碼。 應用程式應避免依賴這些標頭,因為它們的使用會因客戶端、情境和協定而異。 若 Content-LengthTransfer-Encoding 是測試特定情境所必需,則可在撰寫HttpRequestMessageHttpContext時於測試中指定。 欲了解更多資訊,請參閱以下 GitHub 議題: