Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este tema se describen algunas técnicas específicas para controladores de pruebas unitarias en Web API 2. Antes de leer este tema, es posible que quiera leer el tutorial Unit Testing ASP.NET Web API 2, que muestra cómo agregar un proyecto de prueba unitaria a la solución.
Versiones de software usadas en el tutorial
- Visual Studio 2017
- Web API 2
- Moq 4.5.30
Nota:
He usado Moq, pero la misma idea se aplica a cualquier framework de simulación. Moq 4.5.30 (y versiones posteriores) admite Visual Studio 2017, Roslyn y .NET 4.5 y versiones posteriores.
Un patrón común en las pruebas unitarias es "preparar-ejecutar-confirmar":
- Organizar: configure los requisitos previos para que se ejecute la prueba.
- Actúe: realice la prueba.
- Aserción: compruebe que la prueba se realizó correctamente.
En el paso de organización, a menudo usará objetos ficticios o auxiliares. Esto minimiza el número de dependencias, por lo que la prueba se centra en probar una cosa.
Estas son algunas cosas que debes probar con pruebas unitarias en los controladores de la API web.
- La acción devuelve el tipo correcto de respuesta.
- Los parámetros no válidos devuelven la respuesta de error correcta.
- La acción llama al método correcto en el repositorio o el nivel de servicio.
- Si la respuesta incluye un modelo de dominio, compruebe el tipo de modelo.
Estos son algunos de los aspectos generales que se deben probar, pero los detalles dependen de la implementación del controlador. En concreto, hace una gran diferencia si las acciones del controlador devuelven HttpResponseMessage o IHttpActionResult. Para obtener más información sobre estos tipos de resultados, vea Resultados de la acción en Web Api 2.
Probar acciones que devuelven HttpResponseMessage
Este es un ejemplo de un controlador cuyas acciones devuelven HttpResponseMessage.
public class ProductsController : ApiController
{
IProductRepository _repository;
public ProductsController(IProductRepository repository)
{
_repository = repository;
}
public HttpResponseMessage Get(int id)
{
Product product = _repository.GetById(id);
if (product == null)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
return Request.CreateResponse(product);
}
public HttpResponseMessage Post(Product product)
{
_repository.Add(product);
var response = Request.CreateResponse(HttpStatusCode.Created, product);
string uri = Url.Link("DefaultApi", new { id = product.Id });
response.Headers.Location = new Uri(uri);
return response;
}
}
Observe que el controlador usa la inyección de dependencias para insertar un IProductRepository. Esto hace que el controlador sea más fácil de probar, ya que se puede inyectar un repositorio ficticio. La siguiente prueba unitaria verifica que el método Get escribe un Product en el cuerpo de la respuesta. Supongamos que repository es un simulacro IProductRepository.
[TestMethod]
public void GetReturnsProduct()
{
// Arrange
var controller = new ProductsController(repository);
controller.Request = new HttpRequestMessage();
controller.Configuration = new HttpConfiguration();
// Act
var response = controller.Get(10);
// Assert
Product product;
Assert.IsTrue(response.TryGetContentValue<Product>(out product));
Assert.AreEqual(10, product.Id);
}
Es importante establecer Solicitud y configuración en el controlador. De lo contrario, se producirá un error en la prueba con argumentNullException o InvalidOperationException.
Prueba de la generación de vínculos
El Post método llama a UrlHelper.Link para crear vínculos en la respuesta. Esto requiere un poco más de configuración en la prueba unitaria:
[TestMethod]
public void PostSetsLocationHeader()
{
// Arrange
ProductsController controller = new ProductsController(repository);
controller.Request = new HttpRequestMessage {
RequestUri = new Uri("http://localhost/api/products")
};
controller.Configuration = new HttpConfiguration();
controller.Configuration.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
controller.RequestContext.RouteData = new HttpRouteData(
route: new HttpRoute(),
values: new HttpRouteValueDictionary { { "controller", "products" } });
// Act
Product product = new Product() { Id = 42, Name = "Product1" };
var response = controller.Post(product);
// Assert
Assert.AreEqual("http://localhost/api/products/42", response.Headers.Location.AbsoluteUri);
}
La clase UrlHelper necesita la dirección URL de solicitud y los datos de ruta, por lo que la prueba tiene que establecer valores para estos. Otra opción es mock o stub UrlHelper. Con este enfoque, se reemplaza el valor predeterminado de ApiController.Url por una versión ficticia o de código auxiliar que devuelve un valor fijo.
Vamos a reescribir la prueba mediante el marco moq . Instale el Moq paquete NuGet en el proyecto de prueba.
[TestMethod]
public void PostSetsLocationHeader_MockVersion()
{
// This version uses a mock UrlHelper.
// Arrange
ProductsController controller = new ProductsController(repository);
controller.Request = new HttpRequestMessage();
controller.Configuration = new HttpConfiguration();
string locationUrl = "http://location/";
// Create the mock and set up the Link method, which is used to create the Location header.
// The mock version returns a fixed string.
var mockUrlHelper = new Mock<UrlHelper>();
mockUrlHelper.Setup(x => x.Link(It.IsAny<string>(), It.IsAny<object>())).Returns(locationUrl);
controller.Url = mockUrlHelper.Object;
// Act
Product product = new Product() { Id = 42 };
var response = controller.Post(product);
// Assert
Assert.AreEqual(locationUrl, response.Headers.Location.AbsoluteUri);
}
En esta versión, no es necesario configurar ningún dato de ruta, ya que urlHelper ficticio devuelve una cadena constante.
Probar acciones que devuelven IHttpActionResult
En la API web 2, una acción de controlador puede devolver IHttpActionResult, que es análoga a ActionResult en ASP.NET MVC. La interfaz IHttpActionResult define un patrón de comandos para crear respuestas HTTP. En lugar de crear la respuesta directamente, el controlador devuelve un IHttpActionResult. Más adelante, la canalización invoca IHttpActionResult para crear la respuesta. Este enfoque facilita la escritura de pruebas unitarias, ya que puede omitir una gran cantidad de la configuración necesaria para HttpResponseMessage.
Este es un controlador de ejemplo cuyas acciones devuelven IHttpActionResult.
public class Products2Controller : ApiController
{
IProductRepository _repository;
public Products2Controller(IProductRepository repository)
{
_repository = repository;
}
public IHttpActionResult Get(int id)
{
Product product = _repository.GetById(id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
public IHttpActionResult Post(Product product)
{
_repository.Add(product);
return CreatedAtRoute("DefaultApi", new { id = product.Id }, product);
}
public IHttpActionResult Delete(int id)
{
_repository.Delete(id);
return Ok();
}
public IHttpActionResult Put(Product product)
{
// Do some work (not shown).
return Content(HttpStatusCode.Accepted, product);
}
}
En este ejemplo se muestran algunos patrones comunes mediante IHttpActionResult. Veamos cómo hacer pruebas unitarias.
Action devuelve 200 (Ok) con un cuerpo de respuesta
El Get método llama a Ok(product) si se encuentra el producto. En la prueba unitaria, asegúrese de que el tipo de valor devuelto sea OkNegotiatedContentResult y el producto devuelto tenga el identificador correcto.
[TestMethod]
public void GetReturnsProductWithSameId()
{
// Arrange
var mockRepository = new Mock<IProductRepository>();
mockRepository.Setup(x => x.GetById(42))
.Returns(new Product { Id = 42 });
var controller = new Products2Controller(mockRepository.Object);
// Act
IHttpActionResult actionResult = controller.Get(42);
var contentResult = actionResult as OkNegotiatedContentResult<Product>;
// Assert
Assert.IsNotNull(contentResult);
Assert.IsNotNull(contentResult.Content);
Assert.AreEqual(42, contentResult.Content.Id);
}
Observe que la prueba unitaria no ejecuta el resultado de la acción. Puede suponer que el resultado de la acción crea correctamente la respuesta HTTP. (Es por eso que el marco de API web tiene sus propias pruebas unitarias).
Action devuelve 404 (no encontrado)
El Get método llama a NotFound() si no se encuentra el producto. En este caso, la prueba unitaria solo comprueba si el tipo de valor devuelto es NotFoundResult.
[TestMethod]
public void GetReturnsNotFound()
{
// Arrange
var mockRepository = new Mock<IProductRepository>();
var controller = new Products2Controller(mockRepository.Object);
// Act
IHttpActionResult actionResult = controller.Get(10);
// Assert
Assert.IsInstanceOfType(actionResult, typeof(NotFoundResult));
}
Action devuelve 200 (OK) sin cuerpo de respuesta
El Delete método llama Ok() a para devolver una respuesta HTTP 200 vacía. Al igual que en el ejemplo anterior, la prueba unitaria comprueba el tipo de valor devuelto, en este caso OkResult.
[TestMethod]
public void DeleteReturnsOk()
{
// Arrange
var mockRepository = new Mock<IProductRepository>();
var controller = new Products2Controller(mockRepository.Object);
// Act
IHttpActionResult actionResult = controller.Delete(10);
// Assert
Assert.IsInstanceOfType(actionResult, typeof(OkResult));
}
Action devuelve 201 (Creado) con un encabezado Location
El método Post llama a CreatedAtRoute para devolver una respuesta HTTP 201 con un URI en el encabezado de Location. En la prueba unitaria, compruebe que la acción establece los valores de enrutamiento correctos.
[TestMethod]
public void PostMethodSetsLocationHeader()
{
// Arrange
var mockRepository = new Mock<IProductRepository>();
var controller = new Products2Controller(mockRepository.Object);
// Act
IHttpActionResult actionResult = controller.Post(new Product { Id = 10, Name = "Product1" });
var createdResult = actionResult as CreatedAtRouteNegotiatedContentResult<Product>;
// Assert
Assert.IsNotNull(createdResult);
Assert.AreEqual("DefaultApi", createdResult.RouteName);
Assert.AreEqual(10, createdResult.RouteValues["id"]);
}
Action devuelve otro 2xx con un cuerpo de respuesta
El Put método llama a Content para devolver una respuesta HTTP 202 (Aceptada) con un cuerpo de respuesta. Este caso es similar a devolver 200 (CORRECTO), pero la prueba unitaria también debe comprobar el código de estado.
[TestMethod]
public void PutReturnsContentResult()
{
// Arrange
var mockRepository = new Mock<IProductRepository>();
var controller = new Products2Controller(mockRepository.Object);
// Act
IHttpActionResult actionResult = controller.Put(new Product { Id = 10, Name = "Product" });
var contentResult = actionResult as NegotiatedContentResult<Product>;
// Assert
Assert.IsNotNull(contentResult);
Assert.AreEqual(HttpStatusCode.Accepted, contentResult.StatusCode);
Assert.IsNotNull(contentResult.Content);
Assert.AreEqual(10, contentResult.Content.Id);
}