Тестирование ПО промежуточного слоя ASP.NET Core
Автор: Крис Росс (Chris Ross)
ПО промежуточного слоя можно протестировать изолированно с TestServer. Оно предоставляет следующие возможности.
- Создание экземпляров конвейера приложения, содержащего только те компоненты, которые необходимо протестировать.
- Отправка пользовательских запросов для проверки поведения ПО промежуточного слоя.
Преимущества.
- Запросы отправляются в памяти, а не сериализуются по сети.
- Это позволяет избежать дополнительных проблем, таких как управление портами и сертификаты HTTPS.
- Исключения в ПО промежуточного слоя могут напрямую возвращаться к вызывающему тесту.
- Можно настроить структуры данных сервера, например HttpContext, непосредственно в тесте.
Настройка TestServer
В тестовом проекте создайте тест.
Создайте и запустите узел, который использует TestServer.
Добавьте все необходимые службы, используемые ПО промежуточного слоя.
Добавьте в проект ссылку на пакет NuGet
Microsoft.AspNetCore.TestHost
.Настройте конвейер обработки для использования ПО промежуточного слоя для теста.
[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). Проверьте правильность версий пакета на сайте 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.Items или HttpContext.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
, чтобы напрямую передавать состояние сервера. - Не задает некоторые связанные с транспортом заголовки по умолчанию, так как они обычно не относятся к ПО промежуточного слоя. Для получения дополнительных сведений см. следующий раздел.
- Игнорирует позицию
Stream
, передаваемую через StreamContent. HttpClient отправляет весь поток, начиная с начальной позиции, даже если задано положение. Дополнительные сведения см. здесь на GitHub.
Заголовки Content-Length и Transfer-Encoding
TestServer не задает связанные с транспортировкой запросы или заголовки ответа, такие как Content-Length и Transfer-Encoding. Приложения не должны зависеть от этих заголовков, так как их использование определяется клиентом, сценарием и протоколом. Если Content-Length
и Transfer-Encoding
требуются для тестирования конкретного сценария, их можно указать в тесте при создании HttpRequestMessage или HttpContext. См. сведения см. в следующих проблемах GitHub:
ASP.NET Core