Тестирование контроллеров WebAPI
Нужно или нет тестировать контроллеры — я не редко слышу эти споры. Но факт остается фактом — есть случаи когда приложение реализует API для взаимодействия с внешним миром и такой API должен быть задокументирован и протестирован. Поэтому несколько слов о том, как тестировать WebAPI-контроллеры.
Как известно, контроллеры WebAPI могут возвращать результат тремя различными способами:
- Используя объект
HttpResponseMessage
- в этом случае результат ответа генерируется прямо в контроллере и отдается на сторону клиента “как есть”. - Используя обертку
IHttpActionResult
- при этом в процессе обработки запроса он преобразуется вHttpResponseMessage
и отправляется клиенту. - Используя любой другой CLR-объект, содержащий данные - тогда объект сериализуется, упаковывается в сообщение ответа и отправляется в ответ на запрос.
Каждый из этих пунктов меняет подход к реализации контроллера, а значит и влияет на структуру тестов. Поэтому рассмотрим каждый случай отдельно.
HttpResponseMessage
Контроллер в этом случае выглядит так:
public class JoinController : ApiController
{
public HttpResponseMessage JoinAsUser(int id)
{
if (id < 0)
return Request.CreateResponse(HttpStatusCode.BadRequest);
// Логика
return Request.CreateResponse(HttpStatusCode.OK);
}
В результате работы действия контроллера генерируется объект HttpResponseMessage
, содержимое которого мы можем проверить в тесте.
Подготовительные действия в тесте заключаются в создании и инициализации контроллера, а также задании маршрутов:
// Arrage
var controller = new JoinController
{
Request = new HttpRequestMessage
{
RequestUri = new Uri("http://localhost/api/join/joinAsUser")
},
Configuration = new HttpConfiguration(),
RequestContext =
{
RouteData = new HttpRouteData(
route: new HttpRoute(),
values: new HttpRouteValueDictionary
{
{ "controller", "join" },
{ "action", "joinAsUser" }
})
}
};
controller.Configuration.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional });
После чего можно обратиться к действию контроллера и проверить результат:
// Act
var result = controller.JoinAsUser(5);
// Assert
Assert.AreEqual(HttpStatusCode.OK, result.StatusCode);
В данном случае производится проверка на успешный код возврата. Разумеется условия могут быть более сложными, например, включать проверки содержимого ответа.
Итоговый код теста:
[TestMethod]
public void JoinAsUserTest()
{
// Arrage
var controller = new JoinController
{
Request = new HttpRequestMessage
{
RequestUri = new Uri("http://localhost/api/join/joinAsUser")
},
Configuration = new HttpConfiguration(),
RequestContext =
{
RouteData = new HttpRouteData(
route: new HttpRoute(),
values: new HttpRouteValueDictionary
{
{ "controller", "join" },
{ "action", "joinAsUser" }
})
}
};
controller.Configuration.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional });
// Act
var result = controller.JoinAsUser(5);
// Assert
Assert.AreEqual(HttpStatusCode.OK, result.StatusCode);
}
IHttpActionResult
Реализация действия контроллера в этом случае может выглядеть так:
public class JoinController : ApiController
{
public IHttpActionResult Leave(int id)
{
if (id < 0)
return BadRequest();
// Логика
return Ok();
}
IHttpActionResult
- это обертка, которая обладает семантикой, например - запрос обработался успешно, с ошибкой, или возникли проблемы с авторизацией. В базовом классе контроллера WebAPI уже имеется набор заготовленных методов для формирования ответа в таком стиле - Ok()
, Ok<>()
, Redirect()
, BadRequest()
и т.п. Мы также можем создать свои типы ответов.
В этом случае тест будет сводиться к проверке типа ответа и его содержимого:
// Act
var result = controller.Leave(5);
// Assert
Assert.IsInstanceOfType(result, typeof(OkResult));
Как и в предыдущем случае, условия проверки могут быть более сложными.
Итоговый код теста:
[TestMethod]
public void LeaveTest()
{
// Arrage
var controller = new JoinController
{
Request = new HttpRequestMessage
{
RequestUri = new Uri("http://localhost/api/join/leave")
},
Configuration = new HttpConfiguration(),
RequestContext =
{
RouteData = new HttpRouteData(
route: new HttpRoute(),
values: new HttpRouteValueDictionary
{
{ "controller", "join" },
{ "action", "leave" }
})
}
};
controller.Configuration.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional });
// Act
var result = controller.Leave(5);
// Assert
Assert.IsInstanceOfType(result, typeof(OkResult));
}
CLR-объект в качестве результата
Это - частный случай прерыдущих способов. Если нам не требуется управлять HTTP-ответом, а нужно всего лишь вернуть содержимое какого-либо объекта, то можно воспользоваться таким вариантом. Выдача ошибочных статусов кодов тогда будет производится через генерацию исключений.
Контроллер в этом случае выглядит так:
public class JoinController : ApiController
{
public string CheckStatus(int id)
{
if (id < 0)
throw new HttpException(400, "Bad request");
return "Joined";
}
В данном примере в качестве результата отдается строка, но это может быть любой объект.
Тестирование в данном случае наиболее прозрачное - вызывается соответствующее действие и проверяется сам объект:
// Act
var result = controller.CheckStatus(5);
// Assert
Assert.AreEqual("Joined", result);
Полный код теста:
[TestMethod]
public void CheckStatusTest()
{
// Arrage
var controller = new JoinController
{
Request = new HttpRequestMessage
{
RequestUri = new Uri("http://localhost/api/join/сheckStatus")
},
Configuration = new HttpConfiguration(),
RequestContext =
{
RouteData = new HttpRouteData(
route: new HttpRoute(),
values: new HttpRouteValueDictionary
{
{ "controller", "join" },
{ "action", "сheckStatus" }
})
}
};
controller.Configuration.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional });
// Act
var result = controller.CheckStatus(5);
// Assert
Assert.AreEqual("Joined", result);
}
Добавить комментарий