Как вы можете unit test маршрутизировать веб-API ASP.NET?
Я пытаюсь написать некоторые модульные тесты, чтобы гарантировать, что запросы, сделанные в моем веб-API, перенаправлены на ожидаемое действие контроллера API с ожидаемыми аргументами.
Я попытался создать тест с использованием класса HttpServer
, но я получаю 500 ответов от сервера и никакой информации для отладки проблемы.
Есть ли способ создать модульные тесты для маршрутизации веб-сайта ASP.NET Web API?
В идеале я хотел бы создать запрос с помощью HttpClient
и обработать запрос сервера и передать его через ожидаемый процесс маршрутизации.
Ответы
Ответ 1
Я написал сообщение в блоге о маршрутах тестирования и в значительной степени о том, о чем вы спрашиваете:
http://www.strathweb.com/2012/08/testing-routes-in-asp-net-web-api/
Надеюсь, что это поможет.
Дополнительным преимуществом является то, что я использовал отражение, чтобы предоставить методы действий, поэтому вместо использования маршрутов со строками вы добавляете их строго типизированным образом. При таком подходе, если ваши имена действий будут когда-либо изменяться, тесты не будут компилироваться, поэтому вы сможете легко обнаружить ошибки.
Ответ 2
Лучший способ проверить свои маршруты для вашего приложения ASP.NET Web API - это интеграция, проверяющая конечные точки.
Вот простой тестовый пример интеграции для вашего приложения ASP.NET Web API. Это не в основном проверяет ваши маршруты, но это незаметно проверяет их. Кроме того, здесь я использую XUnit, Autofac и Moq.
[Fact, NullCurrentPrincipal]
public async Task
Returns_200_And_Role_With_Key() {
// Arrange
Guid key1 = Guid.NewGuid(),
key2 = Guid.NewGuid(),
key3 = Guid.NewGuid(),
key4 = Guid.NewGuid();
var mockMemSrv = ServicesMockHelper
.GetInitialMembershipService();
mockMemSrv.Setup(ms => ms.GetRole(
It.Is<Guid>(k =>
k == key1 || k == key2 ||
k == key3 || k == key4
)
)
).Returns<Guid>(key => new Role {
Key = key, Name = "FooBar"
});
var config = IntegrationTestHelper
.GetInitialIntegrationTestConfig(GetInitialServices(mockMemSrv.Object));
using (var httpServer = new HttpServer(config))
using (var client = httpServer.ToHttpClient()) {
var request = HttpRequestMessageHelper
.ConstructRequest(
httpMethod: HttpMethod.Get,
uri: string.Format(
"https://localhost/{0}/{1}",
"api/roles",
key2.ToString()),
mediaType: "application/json",
username: Constants.ValidAdminUserName,
password: Constants.ValidAdminPassword);
// Act
var response = await client.SendAsync(request);
var role = await response.Content.ReadAsAsync<RoleDto>();
// Assert
Assert.Equal(key2, role.Key);
Assert.Equal("FooBar", role.Name);
}
}
Есть несколько внешних помощников, которые я использую для этого теста. Один из них - NullCurrentPrincipalAttribute
. Когда ваш тест будет запущен под вашей Windows Identity, Thread.CurrentPrincipal
будет установлен с этим идентификатором. Итак, если вы используете какую-то авторизацию в своем приложении, лучше всего избавиться от этого:
public class NullCurrentPrincipalAttribute : BeforeAfterTestAttribute {
public override void Before(MethodInfo methodUnderTest) {
Thread.CurrentPrincipal = null;
}
}
Затем я создаю макет MembershipService
. Это специфичная для приложения настройка. Таким образом, это будет изменено для вашей собственной реализации.
GetInitialServices
создает для меня контейнер Autofac.
private static IContainer GetInitialServices(
IMembershipService memSrv) {
var builder = IntegrationTestHelper
.GetEmptyContainerBuilder();
builder.Register(c => memSrv)
.As<IMembershipService>()
.InstancePerApiRequest();
return builder.Build();
}
Метод GetInitialIntegrationTestConfig
просто инициализирует мою конфигурацию.
internal static class IntegrationTestHelper {
internal static HttpConfiguration GetInitialIntegrationTestConfig() {
var config = new HttpConfiguration();
RouteConfig.RegisterRoutes(config.Routes);
WebAPIConfig.Configure(config);
return config;
}
internal static HttpConfiguration GetInitialIntegrationTestConfig(IContainer container) {
var config = GetInitialIntegrationTestConfig();
AutofacWebAPI.Initialize(config, container);
return config;
}
}
Метод RouteConfig.RegisterRoutes
в основном регистрирует мои маршруты. У меня также есть небольшой метод расширения для создания HttpClient
по HttpServer
.
internal static class HttpServerExtensions {
internal static HttpClient ToHttpClient(
this HttpServer httpServer) {
return new HttpClient(httpServer);
}
}
Наконец, у меня есть статический класс под названием HttpRequestMessageHelper
, который имеет кучу статических методов для создания нового экземпляра HttpRequestMessage
.
internal static class HttpRequestMessageHelper {
internal static HttpRequestMessage ConstructRequest(
HttpMethod httpMethod, string uri) {
return new HttpRequestMessage(httpMethod, uri);
}
internal static HttpRequestMessage ConstructRequest(
HttpMethod httpMethod, string uri, string mediaType) {
return ConstructRequest(
httpMethod,
uri,
new MediaTypeWithQualityHeaderValue(mediaType));
}
internal static HttpRequestMessage ConstructRequest(
HttpMethod httpMethod, string uri,
IEnumerable<string> mediaTypes) {
return ConstructRequest(
httpMethod,
uri,
mediaTypes.ToMediaTypeWithQualityHeaderValues());
}
internal static HttpRequestMessage ConstructRequest(
HttpMethod httpMethod, string uri, string mediaType,
string username, string password) {
return ConstructRequest(
httpMethod, uri, new[] { mediaType }, username, password);
}
internal static HttpRequestMessage ConstructRequest(
HttpMethod httpMethod, string uri,
IEnumerable<string> mediaTypes,
string username, string password) {
var request = ConstructRequest(httpMethod, uri, mediaTypes);
request.Headers.Authorization = new AuthenticationHeaderValue(
"Basic",
EncodeToBase64(
string.Format("{0}:{1}", username, password)));
return request;
}
// Private helpers
private static HttpRequestMessage ConstructRequest(
HttpMethod httpMethod, string uri,
MediaTypeWithQualityHeaderValue mediaType) {
return ConstructRequest(
httpMethod,
uri,
new[] { mediaType });
}
private static HttpRequestMessage ConstructRequest(
HttpMethod httpMethod, string uri,
IEnumerable<MediaTypeWithQualityHeaderValue> mediaTypes) {
var request = ConstructRequest(httpMethod, uri);
request.Headers.Accept.AddTo(mediaTypes);
return request;
}
private static string EncodeToBase64(string value) {
byte[] toEncodeAsBytes = Encoding.UTF8.GetBytes(value);
return Convert.ToBase64String(toEncodeAsBytes);
}
}
Я использую Basic Authentication в своем приложении. Итак, этот класс имеет некоторые методы, которые строят HttpRequestMessege
с заголовком Аутентификация.
В конце концов, я делаю свой Закон и Assert, чтобы проверить то, что мне нужно. Это может быть образец избыточного количества, но я думаю, что это даст вам отличную идею.
Вот отличная запись в блоге Тестирование интеграции с HttpServer. Кроме того, вот еще один отличный пост в Тестирование маршрутов в веб-интерфейсе ASP.NET.
Ответ 3
Для тестирования маршрутов веб-API ASP.NET вы можете использовать сторонний инструмент, например MvcRouteTester или MyWebApi. Вот пример кода, который поможет вам начать:
MyWebApi
.Routes()
.ShouldMap("api/WebApiController/SomeAction")
.WithHttpMethod(HttpMethod.Post)
.WithJsonContent(@"{""SomeInt"": 1, ""SomeString"": ""Test""}")
.To<WebApiController>(c => c.SomeAction(new RequestModel
{
SomeInt = 1,
SomeString = "Test"
}));
Если вы хотите реализовать тестирование самостоятельно, вы можете посмотреть, как сделать так, чтобы внутренний URL-адрес API-интерфейса маршрутизации отображал запрос для вас. Тогда вам нужно сравнить только выбранный контроллер и действия. ССЫЛКА ЗДЕСЬ.
Ответ 4
hi, когда вы собираетесь тестировать свои маршруты, основной целью является тест GetRouteData() с этим тестом, который вы убедитесь, что система маршрута правильно распознает ваш запрос и выбран правильный маршрут.
[Theory]
[InlineData("http://localhost:5240/foo/route", "GET", false, null, null)]
[InlineData("http://localhost:5240/api/Cars/", "GET", true, "Cars", null)]
[InlineData("http://localhost:5240/api/Cars/123", "GET", true, "Cars", "123")]
public void DefaultRoute_Returns_Correct_RouteData(
string url, string method, bool shouldfound, string controller, string id)
{
//Arrange
var config = new HttpConfiguration();
WebApiConfig.Register(config);
var actionSelector = config.Services.GetActionSelector();
var controllerSelector = config.Services.GetHttpControllerSelector();
var request = new HttpRequestMessage(new HttpMethod(method), url);
config.EnsureInitialized();
//Act
var routeData = config.Routes.GetRouteData(request);
//Assert
// assert
Assert.Equal(shouldfound, routeData != null);
if (shouldfound)
{
Assert.Equal(controller, routeData.Values["controller"]);
Assert.Equal(id == null ? (object)RouteParameter.Optional : (object)id, routeData.
Values["id"]);
}
}
это важно, но недостаточно, даже проверяя, что выбран правильный маршрут, и извлечены правильные данные маршрута, не гарантирует, что
выбран правильный контроллер и действие
это удобный метод, если вы не переписываете службы IHttpActionSelector и IHttpControllerSelector по умолчанию.
[Theory]
[InlineData("http://localhost:12345/api/Cars/123", "GET", typeof(CarsController), "GetCars")]
[InlineData("http://localhost:12345/api/Cars", "GET", typeof(CarsController), "GetCars")]
public void Ensure_Correct_Controller_and_Action_Selected(string url,string method,
Type controllerType,string actionName) {
//Arrange
var config = new HttpConfiguration();
WebApiConfig.Register(config);
var controllerSelector = config.Services.GetHttpControllerSelector();
var actionSelector = config.Services.GetActionSelector();
var request = new HttpRequestMessage(new HttpMethod(method),url);
config.EnsureInitialized();
var routeData = config.Routes.GetRouteData(request);
request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;
request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
//Act
var ctrlDescriptor = controllerSelector.SelectController(request);
var ctrlContext = new HttpControllerContext(config, routeData, request)
{
ControllerDescriptor = ctrlDescriptor
};
var actionDescriptor = actionSelector.SelectAction(ctrlContext);
//Assert
Assert.NotNull(ctrlDescriptor);
Assert.Equal(controllerType, ctrlDescriptor.ControllerType);
Assert.Equal(actionName, actionDescriptor.ActionName);
}
}