Unit Testing ASP.NET DataAnnotations validation Unit Testing ASP.NET DataAnnotations validation asp.net asp.net

Unit Testing ASP.NET DataAnnotations validation


I posted this in my blog post:

using System.ComponentModel.DataAnnotations;// model classpublic class Fiz{    [Required]    public string Name { get; set; }    [Required]    [RegularExpression(".+@..+")]    public string Email { get; set; }}// in test class[TestMethod]public void EmailRequired(){    var fiz = new Fiz         {            Name = "asdf",            Email = null        };    Assert.IsTrue(ValidateModel(fiz).Any(        v => v.MemberNames.Contains("Email") &&              v.ErrorMessage.Contains("required")));}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;}


I was going through http://bradwilson.typepad.com/blog/2009/04/dataannotations-and-aspnet-mvc.html, in this post I didn't like the idea of putting the validation tests in controller test and somewhat manual checking in each test that if the validation attribute exists or not. So, below is the helper method and it's usage which I implemented, it works for both EDM (which has metadata attributes, because of the reason we can not apply attributes on auto generated EDM classes) and POCO objects which have ValidationAttributes applied to their properties.

The helper method does not parse into hierarchical objects, but validation can be tested on flat individual objects(Type-level)

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

this is another post http://johan.driessen.se/archive/2009/11/18/testing-dataannotation-based-validation-in-asp.net-mvc.aspx which talks about validating in .Net 4, but i think i am going to stick to my helper method which is valid in both 3.5 and 4


Validation will be performed by the ModelBinder. In the example, you construct the ShippingDetails yourself, which will skip the ModelBinder and thus, validation entirely. Note the difference between input validation and model validation. Input validation is to make sure the user provided some data, given he had the chance to do so. If you provide a form without the associated field, the associated validator won't be invoked.

There have been changes in MVC2 on model validation vs. input validation, so the exact behaviour depends on the version you are using. See http://bradwilson.typepad.com/blog/2010/01/input-validation-vs-model-validation-in-aspnet-mvc.html for details on this regarding both MVC and MVC 2.

[EDIT] I guess the cleanest solution to this is to call UpdateModel on the Controller manually when testing by providing a custom mock ValueProvider. That should fire validation and set the ModelState correctly.