Pruebas Unitarias ASP.NET Validación de anotaciones de datos


Estoy usando DataAnnotations para la validación de mi modelo, es decir,

    [Required(ErrorMessage="Please enter a name")]
    public string Name { get; set; }

En mi controlador estoy comprobando el valor de ModelState. Esto está devolviendo correctamente false para los datos del modelo no válidos publicados desde mi vista.

Sin embargo, al ejecutar la prueba unitaria de la acción de mi controlador, ModelState siempre devuelve true:

    [TestMethod]
    public void Submitting_Empty_Shipping_Details_Displays_Default_View_With_Error()
    {
        // Arrange
        CartController controller = new CartController(null, null);
        Cart cart = new Cart();
        cart.AddItem(new Product(), 1);

        // Act
        var result = controller.CheckOut(cart, new ShippingDetails() { Name = "" });

        // Assert
        Assert.IsTrue(string.IsNullOrEmpty(result.ViewName));
        Assert.IsFalse(result.ViewData.ModelState.IsValid);
    }

¿Necesito hacer algo adicional para configurar la validación del modelo en mis pruebas?

Gracias,

Ben

Author: Ben Foster, 2010-01-30

5 answers

La validación será realizada por el ModelBinder. En el ejemplo, construyes el ShippingDetails tú mismo, que saltará el ModelBinder y, por lo tanto, la validación por completo. Tenga en cuenta la diferencia entre la validación de entrada y la validación del modelo. La validación de entrada es para asegurarse de que el usuario proporcionó algunos datos, dado que tuvo la oportunidad de hacerlo. Si proporciona un formulario sin el campo asociado, no se invocará el validador asociado.

Ha habido cambios en MVC2 en la validación del modelo frente a la entrada validación, por lo que el comportamiento exacto depende de la versión que esté utilizando. Véase http://bradwilson.typepad.com/blog/2010/01/input-validation-vs-model-validation-in-aspnet-mvc.html para más detalles sobre esto con respecto a MVC y MVC 2.

[EDIT] Supongo que la solución más limpia para esto es llamar a UpdateModel en el Controlador manualmente al probar proporcionando un simulacro personalizado ValueProvider. Esto debería activar la validación y establecer el ModelState correctamente.

 18
Author: mnemosyn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-30 12:42:19

He publicado esto en mi entrada de blog :

// model class
using System.ComponentModel.DataAnnotations;

namespace MvcApplication2.Models
{
    public class Fiz
    {
        [Required]
        public string Name { get; set; }

        [Required]
        [RegularExpression(".+@..+")]
        public string Email { get; set; }
    }
}

// test class
[TestMethod]
public void EmailRequired()
{
    var fiz = new Fiz 
        {
            Name = "asdf",
            Email = null
        };
    Assert.IsTrue(ValidateModel(fiz).Count > 0);
}

private IList<ValidationResult> ValidateModel(object model)
{
    var validationResults = new List<ValidationResult>();
    var ctx = new ValidationContext(model, null, null);
    Validator.TryValidateObject(model, ctx, validationResults, true);
    return validationResults;
}
 90
Author: Jon Davis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-02 05:23:58

Estaba pasando por http://bradwilson.typepad.com/blog/2009/04/dataannotations-and-aspnet-mvc.html, en este post no me gustó la idea de poner las pruebas de validación en controller test y comprobar algo manual en cada prueba si el atributo de validación existe o no. Por lo tanto, a continuación se muestra el método helper y su uso que implementé, funciona tanto para EDM (que tiene atributos de metadatos, debido a la razón por la que no podemos aplicar atributos en las clases de EDM generadas automáticamente) y objetos POCO que tienen ValidationAttributes aplicados a sus propiedades.

El método helper no analiza en objetos jerárquicos, pero la validación se puede probar en objetos individuales planos(nivel de tipo)

class TestsHelper
{

    internal static void ValidateObject<T>(T obj)
    {
        var type = typeof(T);
        var meta = type.GetCustomAttributes(false).OfType<MetadataTypeAttribute>().FirstOrDefault();
        if (meta != null)
        {
            type = meta.MetadataClassType;
        }
        var propertyInfo = type.GetProperties();
        foreach (var info in propertyInfo)
        {
            var attributes = info.GetCustomAttributes(false).OfType<ValidationAttribute>();
            foreach (var attribute in attributes)
            {
                var objPropInfo = obj.GetType().GetProperty(info.Name);
                attribute.Validate(objPropInfo.GetValue(obj, null), info.Name);
            }
        }
    }
}

 /// <summary>
/// Link EDM class with meta data class
/// </summary>
[MetadataType(typeof(ServiceMetadata))]
public partial class Service
{
}

/// <summary>
/// Meta data class to hold validation attributes for each property
/// </summary>
public class ServiceMetadata
{
    /// <summary>
    /// Name 
    /// </summary>
    [Required]
    [StringLength(1000)]
    public object Name { get; set; }

    /// <summary>
    /// Description
    /// </summary>
    [Required]
    [StringLength(2000)]
    public object Description { get; set; }
}


[TestFixture]
public class ServiceModelTests 
{
    [Test]
    [ExpectedException(typeof(ValidationException), ExpectedMessage = "The Name field is required.")]
    public void Name_Not_Present()
    {
        var serv = new Service{Name ="", Description="Test"};
        TestsHelper.ValidateObject(serv);
    }

    [Test]
    [ExpectedException(typeof(ValidationException), ExpectedMessage = "The Description field is required.")]
    public void Description_Not_Present()
    {
        var serv = new Service { Name = "Test", Description = string.Empty};
        TestsHelper.ValidateObject(serv);
    }

}

Este es otro post http://johan.driessen.se/archive/2009/11/18/testing-dataannotation-based-validation-in-asp.net-mvc.aspx que habla de validar en. Net 4, pero creo que voy a seguir mi método helper que es válido tanto en 3.5 y 4

 23
Author: scorpio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-04 09:00:02

Me gusta probar los atributos de datos en mis modelos y ver modelos fuera del contexto del controlador. He hecho esto escribiendo mi propia versión de TryUpdateModel que no necesita un controlador y se puede utilizar para rellenar un diccionario ModelState.

Aquí está mi método TryUpdateModel (tomado principalmente del código fuente del controlador MVC. NET):

private static ModelStateDictionary TryUpdateModel<TModel>(TModel model,
        IValueProvider valueProvider) where TModel : class
{
    var modelState = new ModelStateDictionary();
    var controllerContext = new ControllerContext();

    var binder = ModelBinders.Binders.GetBinder(typeof(TModel));
    var bindingContext = new ModelBindingContext()
    {
        ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
            () => model, typeof(TModel)),
        ModelState = modelState,
        ValueProvider = valueProvider
    };
    binder.BindModel(controllerContext, bindingContext);
    return modelState;
}

Esto se puede usar fácilmente en una prueba unitaria como esta:

// Arrange
var viewModel = new AddressViewModel();
var addressValues = new FormCollection
{
    {"CustomerName", "Richard"}
};

// Act
var modelState = TryUpdateModel(viewModel, addressValues);

// Assert
Assert.False(modelState.IsValid);
 7
Author: Richard Garside,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-26 13:55:05

Tuve un problema donde TestsHelper trabajaba la mayor parte del tiempo, pero no para los métodos de validación definidos por la interfaz IValidatableObject. El CompareAttribute también me dio algunos problemas. Es por eso que el try/catch está ahí. El siguiente código parece validar todos los casos:

public static void ValidateUsingReflection<T>(T obj, Controller controller)
{
    ValidationContext validationContext = new ValidationContext(obj, null, null);
    Type type = typeof(T);
    MetadataTypeAttribute meta = type.GetCustomAttributes(false).OfType<MetadataTypeAttribute>().FirstOrDefault();
    if (meta != null)
    {
        type = meta.MetadataClassType;
    }
    PropertyInfo[] propertyInfo = type.GetProperties();
    foreach (PropertyInfo info in propertyInfo)
    {
        IEnumerable<ValidationAttribute> attributes = info.GetCustomAttributes(false).OfType<ValidationAttribute>();
        foreach (ValidationAttribute attribute in attributes)
        {
            PropertyInfo objPropInfo = obj.GetType().GetProperty(info.Name);
            try
            {
                validationContext.DisplayName = info.Name;
                attribute.Validate(objPropInfo.GetValue(obj, null), validationContext);
            }
            catch (Exception ex)
            {
                controller.ModelState.AddModelError(info.Name, ex.Message);
            }
        }
    }
    IValidatableObject valObj = obj as IValidatableObject;
    if (null != valObj)
    {
        IEnumerable<ValidationResult> results = valObj.Validate(validationContext);
        foreach (ValidationResult result in results)
        {
            string key = result.MemberNames.FirstOrDefault() ?? string.Empty;
            controller.ModelState.AddModelError(key, result.ErrorMessage);
        }
    }
}
 1
Author: Vance Kessler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-01 20:25:56