Pregunta

¿Cómo puedo probar que mi controlador de acción es poner la corrección de errores en el ModelState el momento de la validación de la entidad, cuando estoy usando DataAnnotation de validación en MVC 2 vista previa de 1?

Algún código para ilustrar.En primer lugar, la acción:

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

Y aquí está una de las fallas de la unidad de prueba que creo que debe estar pasando, pero no lo es (usando MbUnit & 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 en adición a esta pregunta, debe Voy a ser la prueba de validación, y debería ser la prueba de esta manera?

¿Fue útil?

Solución

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

Esto dará lugar a la validación de cualquier campo de la 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);
    }

Sólo asegúrese de que su prueba añade un valor nulo para todos los campos de las vistas de formulario que desea dejar vacía.

He descubierto que hacerlo de esta manera, a expensas de unas pocas líneas adicionales de código, hace que mis pruebas de unidad se asemejan a la forma en que el código se llama en tiempo de ejecución más de cerca haciéndolos más valioso. También puede probar lo que sucede cuando alguien entra en "abc" en un control enlazado a una propiedad int.

Otros consejos

Odio a necro un puesto de edad, pero pensé que me gustaría añadir mis propios pensamientos (ya que sólo tenía este problema y encontré con este post, mientras que la búsqueda de la respuesta).

  1. No se debe pasar la validación de las pruebas del controlador. Ya sea de su confianza de validación de MVC o escribe su propio (es decir, no probar el código de otra, probar su código)
  2. Si desea probar la validación se realiza lo que espera, probarlo en sus ensayos con modelos (Lo hago por un par de mis validaciones de expresiones regulares más complejas).

Lo que realmente desea probar aquí es que su controlador hace lo que esperas que hacer cuando falla la validación. Ese es su código, y sus expectativas. Probando que es fácil una vez que se da cuenta de que es todo lo que quiere hacer la prueba:

[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));
}

Yo había estado teniendo el mismo problema, y después de leer Pauls respuesta y comentario, me miró de un modo de manual de validar el modelo de vista.

He encontrado este tutorial que explica cómo validar manualmente un ViewModel que utiliza DataAnnotations.La Clave del fragmento de código es hacia el final del post.

He modificado el código ligeramente - en el tutorial el 4º parámetro de la TryValidateObject se omite (validateAllProperties).Con el fin de obtener todas las anotaciones para Validar, esto se debe establecer en true.

Además me refactorizado el código en un método genérico, para hacer pruebas de ViewModel validación 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 ha funcionado muy bien para nosotros.

Cuando se llama al método homeController.Index en su prueba, que no está utilizando cualquiera de los framework MVC que dispara la validación de modo ModelState.IsValid siempre será cierto. En nuestro código que llamamos un método de ayuda Validar directamente en el controlador en lugar de utilizar la validación ambiente. No he tenido mucha experiencia con los DataAnnotations (Utilizamos NHibernate.Validators) tal vez alguien más puede ofrecer orientación cómo llamar Validar desde el interior de su controlador.

yo estaba investigando esto hoy y encontré esta entrada del blog por Roberto Hernández (MVP) que parece ofrecer la mejor solución para disparar los validadores para una acción de controlador durante la prueba de la unidad. Esto hará que los errores correctos en el ModelState al validar una entidad.

Estoy usando ModelBinders en mis casos de prueba para poder actualizar el valor 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 marco 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);
        }

Esto no responde exactamente a su pregunta, ya que abandona DataAnnotations, pero voy a añadir, ya que podría ayudar a otras personas escribir pruebas para sus controladores:

Usted tiene la opción de no usar la validación proporcionada por System.ComponentModel.DataAnnotations pero aún utilizando el objeto ViewData.ModelState, mediante el uso de su método AddModelError y algún otro mecanismo de validación. Por ejemplo:

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 tomar ventaja de las cosas que Html.ValidationMessageFor() MVC genera, sin utilizar el DataAnnotations. Usted tiene que asegurarse de que la clave se utiliza con AddModelError coincide con lo que la vista está a la espera de mensajes de validación.

El controlador se convierte entonces en comprobable porque la validación está ocurriendo de forma explícita, en lugar de realizarse automagicamente por el marco MVC.

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

Sin embargo, puede también prueba de la unidad que su modelo / modelo de vista tiene los atributos de validación correctas definido. Digamos que su modelo de vista es el siguiente:

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

Esta prueba unidad comprobar 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);
}

A diferencia de ARM, no tengo un problema con grave excavación. 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 en la búsqueda de respuestas y no puedo probar en MVC2). En lugar de poner el código de validación de un método estático genérica, lo pongo en un controlador de prueba. El controlador dispone de todo lo necesario para su validación. Por lo tanto, el controlador de prueba es el siguiente:

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 tiene por qué ser un innerclass protegida, que es la forma en que uso ahora, pero probablemente voy a volver a utilizar esa clase. Si en algún lugar hay un modelo MyModel que está decorado con bonitos atributos de anotación de datos, entonces la prueba es como la siguiente:

    [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 puede reutilizar el controlador de prueba para las pruebas de todos mis modelos y puede ser capaz de extender a burlarse un poco más sobre el controlador o utilizar los métodos protegidas que un controlador tiene.

Espero que ayuda.

Si usted se preocupa por la validación pero no se preocupan por la forma en que se lleva a cabo, si sólo se preocupan por la validación de su método de acción al más alto nivel de abstracción, no importa si se implementa como el uso de DataAnnotations, o incluso ModelBinders ActionFilterAttributes, entonces usted podría utilizar el paquete Nuget 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();

En base a @ Giles-Smith 's respuesta y comentarios, para la API de 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 sobre la respuesta de texto de arriba ...

La respuesta de

@ Giles-Smith es mi método preferido, pero la ejecución puede simplificarse:

    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);
        }
    }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top