Espero que esta pregunta dé algunas respuestas interesantes porque es una que me ha molestado por un tiempo.
¿Hay algún valor real en la unidad que prueba un controlador en ASP.NET MVC?
Lo que quiero decir con eso es que, la mayoría de las veces, (y no soy un genio), mis métodos de controlador son, incluso en su forma más compleja, algo como esto:
public ActionResult Create(MyModel model)
{
// start error list
var errors = new List<string>();
// check model state based on data annotations
if(ModelState.IsValid)
{
// call a service method
if(this._myService.CreateNew(model, Request.UserHostAddress, ref errors))
{
// all is well, data is saved,
// so tell the user they are brilliant
return View("_Success");
}
}
// add errors to model state
errors.ForEach(e => ModelState.AddModelError("", e));
// return view
return View(model);
}
La mayor parte del trabajo pesado se realiza mediante la tubería MVC o mi biblioteca de servicio.
Entonces, tal vez las preguntas para hacer podrían ser:
- ¿Cuál sería el valor de la unidad que prueba este método?
- ¿no se rompería
Request.UserHostAddress
yModelState
con una NullReferenceException? ¿Debo tratar de burlarme de estos? - si refractaria este método en un "ayudante" reutilizable (¡lo cual probablemente debería hacer, teniendo en cuenta cuántas veces lo hago!), ¿probaría que incluso valdría la pena cuando todo lo que realmente estoy probando es principalmente la "tubería" que, presumiblemente, ¿ha sido probado a una pulgada de su vida por Microsoft?
Creo que mi punto es realmente , hacer lo siguiente parece completamente inútil e incorrecto
[TestMethod]
public void Test_Home_Index()
{
var controller = new HomeController();
var expected = "Index";
var actual = ((ViewResult)controller.Index()).ViewName;
Assert.AreEqual(expected, actual);
}
Obviamente estoy siendo obtuso con este ejemplo exageradamente inútil, pero ¿alguien tiene alguna sabiduría para agregar aquí?
Estoy deseando que llegue ... Gracias.
fuente
Respuestas:
Incluso para algo tan simple, una prueba unitaria tendrá múltiples propósitos
Para esa acción en particular, probaría lo siguiente
Usted señaló la comprobación de Solicitud y Modelo para NullReferenceException y creo que ModelState.IsValid se encargará de manejar NullReference para Modelo.
Mockear la solicitud le permite protegerse contra una solicitud nula, que generalmente es imposible en la producción, creo, pero puede suceder en una prueba de unidad. En una prueba de integración, le permitiría proporcionar diferentes valores de UserHostAddress (una solicitud aún es ingresada por el usuario en lo que respecta al control y debe probarse en consecuencia)
fuente
Mis controladores también son muy pequeños. La mayor parte de la "lógica" en los controladores se maneja utilizando atributos de filtro (incorporados y escritos a mano). Por lo tanto, mi controlador generalmente solo tiene un puñado de trabajos:
ActionResult
ASP.NET MVC realiza la mayor parte del enlace del modelo automáticamente. DataAnnotations también maneja la mayor parte de la validación para mí.
Incluso con tan poco para probar, todavía los escribo normalmente. Básicamente, pruebo que se llama a mis repositorios y que
ActionResult
se devuelve el tipo correcto . Tengo un método conveniente paraViewResult
asegurarme de que se devuelva la ruta de vista correcta y que el modelo de vista se vea como yo esperaba. Tengo otro para verificar que el controlador / acción correcto está configuradoRedirectToActionResult
. Tengo otras pruebas paraJsonResult
, etc. etc.Un resultado desafortunado de subclasificar la
Controller
clase es que proporciona muchos métodos convenientes que utilizanHttpContext
internamente. Esto dificulta la prueba unitaria del controlador. Por esta razón, normalmente pongoHttpContext
llamadas dependientes detrás de una interfaz y paso esa interfaz al constructor del controlador (uso la extensión web Ninject para crear mis controladores por mí). Esta interfaz es generalmente donde pego las propiedades de ayuda para acceder a la sesión, los ajustes de configuración, el IPrinciple y los ayudantes de URL.Esto requiere mucha diligencia debida, pero creo que vale la pena.
fuente
BaseControllerTests
clase donde viven todos. Me burlo de mis repositorios. Los conecto usando Ninject.ActionResult
s para inspeccionar las URL, modelos, etc. pasadosObviamente, algunos controladores son mucho más complejos que eso, pero se basan únicamente en su ejemplo:
¿Qué sucede si myService lanza una excepción?
Como nota al margen.
Además, cuestionaría la sabiduría de pasar una lista por referencia (es innecesario ya que C # pasa por referencia de todos modos, pero incluso si no fuera así), pasando una acción de Acción de error (Acción) que el servicio puede usar para enviar mensajes de error a que podría manejarse como lo desee (tal vez desee agregarlo a la lista, tal vez desee agregar un error de modelo, tal vez desee registrarlo).
En tu ejemplo:
en lugar de errores de referencia, haga (string s) => ModelState.AddModelError ("", s) por ejemplo.
fuente