Kiểm thử ASP.NET Core Middleware (Phần mềm trung gian)
Nguồn: Test ASP.NET Core middleware
Bởi Chris Ross
Middleware có thể được kiểm thử độc lập với TestServer. Nó cho phép bạn:
- Khởi tạo một app pipeline (đường ống ứng dụng) chỉ chứa các thành phần cần kiểm thử.
- Gửi các request (yêu cầu) tùy chỉnh để kiểm tra hành vi middleware.
Ưu điểm:
- Các request được gửi in-memory (trong bộ nhớ) thay vì được serialize (tuần tự hóa) qua mạng.
- Điều này tránh các vấn đề bổ sung như quản lý port và chứng chỉ HTTPS.
- Các exception (ngoại lệ) trong middleware có thể luồng trực tiếp trở lại test đang gọi.
- Có thể tùy chỉnh các cấu trúc dữ liệu server, chẳng hạn như
HttpContext, trực tiếp trong test.
Thiết lập TestServer
Trong project test, tạo một test:
- Build và khởi động một host sử dụng
TestServer. - Thêm bất kỳ services (dịch vụ) nào mà middleware cần dùng.
- Thêm package reference vào project cho NuGet package
Microsoft.AspNetCore.TestHost. - Cấu hình processing pipeline (đường ống xử lý) để sử dụng middleware cho test.
```csharp [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();
... } ```
Gửi request với HttpClient
Gửi một request bằng 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("/");
...
}Kiểm tra kết quả. Trước tiên, thực hiện một assertion (khẳng định) ngược lại với kết quả mà bạn mong đợi. Lần chạy ban đầu với assertion false positive xác nhận rằng test thất bại khi middleware hoạt động đúng. Chạy test và xác nhận rằng test thất bại.
Trong ví dụ sau, middleware phải trả về status code (mã trạng thái) 404 (Not Found) khi root endpoint (endpoint gốc) được yêu cầu. Thực hiện lần chạy test đầu tiên với Assert.NotEqual( ... );, điều này sẽ thất bại:
[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);
}Thay đổi assertion để kiểm thử middleware trong điều kiện hoạt động bình thường. Test cuối cùng dùng Assert.Equal( ... );. Chạy lại test để xác nhận nó pass (thành công).
[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);
}Gửi request với HttpContext
Ứng dụng test cũng có thể gửi request bằng SendAsync(Action<HttpContext>, CancellationToken). Trong ví dụ sau, một số kiểm tra được thực hiện khi https://example.com/A/Path/?and=query được middleware xử lý:
[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 cho phép cấu hình trực tiếp đối tượng HttpContext thay vì sử dụng abstraction (trừu tượng hóa) HttpClient. Dùng SendAsync để thao tác các cấu trúc chỉ có sẵn trên server, chẳng hạn như HttpContext.Items hoặc HttpContext.Features.
Giống với ví dụ trước kiểm tra response 404 - Not Found, hãy kiểm tra ngược lại cho mỗi câu lệnh Assert trong test trên. Việc kiểm tra xác nhận rằng test thất bại đúng cách khi middleware hoạt động bình thường. Sau khi xác nhận false positive test hoạt động, đặt các câu lệnh Assert cuối cùng cho các điều kiện và giá trị mong đợi của test. Chạy lại để xác nhận test pass.
Thêm route (tuyến đường) cho request
Các route bổ sung có thể được thêm bằng cách cấu hình sử dụng HttpClient test:
[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);
}Các route bổ sung cũng có thể được thêm bằng cách dùng server.SendAsync.
Giới hạn của TestServer
TestServer:
- Được tạo ra để sao chép hành vi server nhằm kiểm thử middleware.
- Không cố gắng sao chép tất cả hành vi
HttpClient. - Cố gắng cung cấp cho client quyền kiểm soát server nhiều nhất có thể, và hiển thị rõ nhất những gì đang xảy ra trên server. Ví dụ, nó có thể throw (ném) các exception thường không được throw bởi
HttpClientđể truyền đạt trực tiếp trạng thái server. - Mặc định không đặt một số transport-specific headers (header vận chuyển cụ thể) vì chúng thường không liên quan đến middleware.
- Bỏ qua vị trí
Streamđược truyền quaStreamContent.HttpClientgửi toàn bộ stream từ vị trí bắt đầu, ngay cả khi positioning được đặt.
Headers Content-Length và Transfer-Encoding
TestServer không đặt các request hoặc response headers liên quan đến transport như Content-Length hay Transfer-Encoding. Ứng dụng nên tránh phụ thuộc vào các headers này vì việc sử dụng của chúng thay đổi theo client, scenario (kịch bản) và protocol (giao thức). Nếu Content-Length và Transfer-Encoding cần thiết để kiểm thử một scenario cụ thể, chúng có thể được chỉ định trong test khi soạn HttpRequestMessage hoặc HttpContext.