ventajas tutorial que programacion net mvc microsoft estructura diagrama capas asp asp.net-mvc unit-testing validation asp.net-mvc-2 tdd

asp.net mvc - tutorial - Pruebas unitarias en la validación de MVC



que es mvc en programacion (12)

¿Cómo puedo probar que la acción de mi controlador está poniendo los errores correctos en el estado del modelo al validar una entidad, cuando estoy usando la validación de la anotación de datos en MVC 2 Preview 1?

Un poco de código para ilustrar. Primero, la acción:

[HttpPost] public ActionResult Index(BlogPost b) { if(ModelState.IsValid) { _blogService.Insert(b); return(View("Success", b)); } return View(b); }

Y aquí hay una prueba de unidad fallida que creo que debería pasar, pero no lo es (utilizando MbUnit y Moq):

[Test] public void When_processing_invalid_post_HomeControllerModelState_should_have_at_least_one_error() { // arrange var mockRepository = new Mock<IBlogPostSVC>(); var homeController = new HomeController(mockRepository.Object); // act var p = new BlogPost { Title = "test" }; // date and content should be required homeController.Index(p); // assert Assert.IsTrue(!homeController.ModelState.IsValid); }

Supongo que además de esta pregunta, ¿ debería probar la validación y debería probarla de esta manera?


A diferencia de ARM, no tengo ningún problema con la excavación de tumbas. Así que aquí está mi sugerencia. Se basa en la respuesta de Giles Smith y trabaja para ASP.NET MVC4 (sé que la pregunta es sobre MVC 2, pero Google no discrimina al buscar respuestas y no puedo probar en MVC2). En lugar de poner el código de validación en un método estático genérico, lo puse en un controlador de prueba. El controlador tiene todo lo necesario para la validación. Entonces, el controlador de prueba se ve así:

using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Wbe.Mvc; protected class TestController : Controller { public void TestValidateModel(object Model) { ValidationContext validationContext = new ValidationContext(Model, null, null); List<ValidationResult> validationResults = new List<ValidationResult>(); Validator.TryValidateObject(Model, validationContext, validationResults, true); foreach (ValidationResult validationResult in validationResults) { this.ModelState.AddModelError(String.Join(", ", validationResult.MemberNames), validationResult.ErrorMessage); } } }

Por supuesto, la clase no necesita ser una clase interna protegida, así es como la uso ahora, pero probablemente voy a reutilizar esa clase. Si en alguna parte hay un modelo MyModel que está decorado con agradables atributos de anotación de datos, entonces la prueba se ve más o menos así:

[TestMethod()] public void ValidationTest() { MyModel item = new MyModel(); item.Description = "This is a unit test"; item.LocationId = 1; TestController testController = new TestController(); testController.TestValidateModel(item); Assert.IsTrue(testController.ModelState.IsValid, "A valid model is recognized."); }

La ventaja de esta configuración es que puedo reutilizar el controlador de prueba para las pruebas de todos mis modelos y es posible que pueda ampliarlo para que se burle un poco más del controlador o use los métodos protegidos que tiene un controlador.

Espero eso ayude.


Basado en las respuestas y comentarios de @ giles-smith, para la API web:

public static void ValidateViewModel<TViewModel, TController>(this TController controller, TViewModel viewModelToValidate) where TController : ApiController { var validationContext = new ValidationContext(viewModelToValidate, null, null); var validationResults = new List<ValidationResult>(); Validator.TryValidateObject(viewModelToValidate, validationContext, validationResults, true); foreach (var validationResult in validationResults) { controller.ModelState.AddModelError(validationResult.MemberNames.FirstOrDefault() ?? string.Empty, validationResult.ErrorMessage); } }

Ver en la edición de respuestas arriba ...


Cuando llama al método homeController.Index en su prueba, no está utilizando ninguno de los framework MVC que desactive la validación para que ModelState.IsValid siempre sea verdadero. En nuestro código, llamamos un método Validate auxiliar directamente en el controlador en lugar de utilizar la validación ambiental. No he tenido mucha experiencia con las anotaciones de datos (utilizamos NHibernate.Validators), tal vez otra persona pueda ofrecerle orientación sobre cómo llamar a Validate desde su controlador.


En lugar de pasar un BlogPost , también puede declarar el parámetro de acciones como FormCollection . Luego puede crear el BlogPost usted mismo y llamar a UpdateModel(model, formCollection.ToValueProvider()); .

Esto activará la validación de cualquier campo en FormCollection .

[HttpPost] public ActionResult Index(FormCollection form) { var b = new BlogPost(); TryUpdateModel(model, form.ToValueProvider()); if (ModelState.IsValid) { _blogService.Insert(b); return (View("Success", b)); } return View(b); }

Solo asegúrese de que su prueba agregue un valor nulo para cada campo en el formulario de vistas que desea dejar vacío.

Descubrí que hacerlo de esta manera, a expensas de unas pocas líneas adicionales de código, hace que mis pruebas unitarias se asemejen a la forma en que se llama el código en tiempo de ejecución más de cerca, lo que las hace más valiosas. También puede probar qué sucede cuando alguien ingresa "abc" en un control vinculado a una propiedad int.


Esto no responde exactamente a su pregunta, porque abandona DataAnnotations, pero la agregaré porque podría ayudar a otras personas a escribir pruebas para sus Controladores:

Tiene la opción de no utilizar la validación proporcionada por System.ComponentModel.DataAnnotations pero aún utilizando el objeto AddModelError , utilizando su método AddModelError y algún otro mecanismo de validación. P.ej:

public ActionResult Create(CompetitionEntry competitionEntry) { if (competitionEntry.Email == null) ViewData.ModelState.AddModelError("CompetitionEntry.Email", "Please enter your e-mail"); if (ModelState.IsValid) { // insert code to save data here... // ... return Redirect("/"); } else { // return with errors var viewModel = new CompetitionEntryViewModel(); // insert code to populate viewmodel here ... // ... return View(viewModel); } }

Esto todavía le permite aprovechar el material Html.ValidationMessageFor() que genera MVC, sin utilizar las DataAnnotations . AddModelError asegurarse de que la clave que usa con AddModelError coincida con lo que espera la vista para los mensajes de validación.

Luego, el controlador se puede probar porque la validación está ocurriendo explícitamente, en lugar de que el marco MVC lo haga de forma automática.


Estoy de acuerdo en que ARM tiene la mejor respuesta: prueba el comportamiento de tu controlador, no la validación incorporada.

Sin embargo, también puede probar por unidad que su modelo / modelo de vista tiene definidos los atributos de validación correctos. Digamos que su ViewModel se ve así:

public class PersonViewModel { [Required] public string FirstName { get; set; } }

Esta prueba unitaria evaluará la existencia del atributo [Required] :

[TestMethod] public void FirstName_should_be_required() { var propertyInfo = typeof(PersonViewModel).GetProperty("FirstName"); var attribute = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), false) .FirstOrDefault(); Assert.IsNotNull(attribute); }


Estoy usando ModelBinders en mis casos de prueba para poder actualizar el valor de model.IsValid.

var form = new FormCollection(); form.Add("Name", "0123456789012345678901234567890123456789"); var model = MvcModelBinder.BindModel<AddItemModel>(controller, form); ViewResult result = (ViewResult)controller.Add(model);

Con mi método MvcModelBinder.BindModel de la siguiente manera (básicamente el mismo código utilizado internamente en el framework MVC):

public static TModel BindModel<TModel>(Controller controller, IValueProvider valueProvider) where TModel : class { IModelBinder binder = ModelBinders.Binders.GetBinder(typeof(TModel)); ModelBindingContext bindingContext = new ModelBindingContext() { FallbackToEmptyPrefix = true, ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(TModel)), ModelName = "NotUsedButNotNull", ModelState = controller.ModelState, PropertyFilter = (name => { return true; }), ValueProvider = valueProvider }; return (TModel)binder.BindModel(controller.ControllerContext, bindingContext); }


Estuve investigando esto hoy y encontré esta publicación de blog de Roberto Hernández (MVP) que parece proporcionar la mejor solución para activar los validadores para una acción de controlador durante la prueba unitaria. Esto colocará los errores correctos en el modelo de estado al validar una entidad.


He estado teniendo el mismo problema, y ​​después de leer la respuesta y el comentario de Pauls, busqué una forma de validar manualmente el modelo de vista.

Encontré este tutorial que explica cómo validar manualmente un ViewModel que usa DataAnnotations. El fragmento de código Key está hacia el final de la publicación.

Modifiqué el código ligeramente: en el tutorial se omite el 4º parámetro del TryValidateObject (validateAllProperties). Para obtener todas las anotaciones para Validar, esto debe establecerse en verdadero.

Adicionalmente reestructuré el código en un método genérico, para hacer que la validación de ViewModel sea simple:

public static void ValidateViewModel<TViewModel, TController>(this TController controller, TViewModel viewModelToValidate) where TController : ApiController { var validationContext = new ValidationContext(viewModelToValidate, null, null); var validationResults = new List<ValidationResult>(); Validator.TryValidateObject(viewModelToValidate, validationContext, validationResults, true); foreach (var validationResult in validationResults) { controller.ModelState.AddModelError(validationResult.MemberNames.FirstOrDefault() ?? string.Empty, validationResult.ErrorMessage); } }

Hasta ahora, esto ha funcionado muy bien para nosotros.


La respuesta de @ giles-smith es mi enfoque preferido, pero la implementación se puede simplificar:

public static void ValidateViewModel(this Controller controller, object viewModelToValidate) { var validationContext = new ValidationContext(viewModelToValidate, null, null); var validationResults = new List<ValidationResult>(); Validator.TryValidateObject(viewModelToValidate, validationContext, validationResults, true); foreach (var validationResult in validationResults) { controller.ModelState.AddModelError(validationResult.MemberNames.FirstOrDefault() ?? string.Empty, validationResult.ErrorMessage); } }


Odio necro una publicación anterior, pero pensé en agregar mis propios pensamientos (ya que acabo de tener este problema y encontré este post mientras buscaba la respuesta).

  1. No pruebe la validación en las pruebas de su controlador. O confía en la validación de MVC o escribe la suya (es decir, no pruebe el código de otros, pruebe su código)
  2. Si desea probar que la validación está haciendo lo que espera, pruébela en las pruebas de su modelo (esto lo hago para un par de mis validaciones de expresiones regulares más complejas).

Lo que realmente quiere probar aquí es que su controlador hace lo que espera que haga cuando la validación falla. Ese es tu código y tus expectativas. Probarlo es fácil una vez que se da cuenta de que eso es todo lo que quiere probar:

[test] public void TestInvalidPostBehavior() { // arrange var mockRepository = new Mock<IBlogPostSVC>(); var homeController = new HomeController(mockRepository.Object); var p = new BlogPost(); homeController.ViewData.ModelState.AddModelError("Key", "ErrorMessage"); // Values of these two strings don''t matter. // What I''m doing is setting up the situation: my controller is receiving an invalid model. // act var result = (ViewResult) homeController.Index(p); // assert result.ForView("Index") Assert.That(result.ViewData.Model, Is.EqualTo(p)); }


Si le importa la validación pero no le importa cómo se implementa, si solo le importa la validación de su método de acción en el nivel más alto de abstracción, sin importar si se implementa como el uso de Anotaciones de datos, Billeteros de modelo o incluso Atributo de filtro de acción, entonces podría usar el paquete nutra de Xania.AspNet.Simulator de la siguiente manera:

install-package Xania.AspNet.Simulator

-

var action = new BlogController() .Action(c => c.Index(new BlogPost()), "POST"); var modelState = action.ValidateRequest(); modelState.IsValid.Should().BeFalse();