Module Testing ASP.NET DataAnnotations Data Validation

I use DataAnnotations for my validation ie model

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

In my controller, I check the value of ModelState. This correctly returns false for invalid model data published from my view.

However, when performing a unit test of my action with the controller, ModelState always returns 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); } 

Do I need to do something to set up model validation in my tests?

Thank,

Ben

+65
asp.net-mvc data-annotations
Jan 30 '10 at 12:03
source share
5 answers

Validation will be performed using ModelBinder . In the example, you yourself create ShippingDetails , which will skip the ModelBinder and thus be fully tested. Note the difference between input validation and model validation. Checking the login is to make sure that the user has provided some data, given that he had such an opportunity. If you provide a form without a corresponding field, the validator associated with it will not be called.

MVC2 has changed in relation to model validation compared to input validation, so the exact behavior 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 MVC and MVC 2 for more details.

[EDIT] I think the cleanest solution for this is to manually call UpdateModel on the controller when testing by providing a custom layout for ValueProvider . This should eliminate the validation and install ModelState .

+19
Jan 30 '10 at 12:24
source share

I posted this in my blog post :

 // 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; } 
+103
Dec 02 '10 at 5:23
source share

I went through http://bradwilson.typepad.com/blog/2009/04/dataannotations-and-aspnet-mvc.html , in this post I did not like the idea of ​​passing verification tests in the control test and manually checking a little in each test. what if the validation attribute exists or not. So, below is the helper method and its use, which I implemented, it works both for EDM (which has metadata attributes, because of the reason why we cannot apply attributes for automatically generated EDM classes), and for POCO objects that have ValidationAttributes applied to their properties.

The helper method does not analyze hierarchical objects, but the check can be checked on flat separate objects (level type)

 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 checking in .Net 4, but I think that I will stick to my helper method, which is valid for both 3.5 and 4

+23
03 Feb '10 at 17:36
source share

I like to test data attributes on models and view models outside the controller context. I did this by writing my own version of TryUpdateModel, which does not need a controller and can be used to populate the ModelState dictionary.

Here is my TryUpdateModel method (mostly taken from the source code of the .NET MVC controller):

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

This can then be easily used in the unit test as follows:

 // Arrange var viewModel = new AddressViewModel(); var addressValues = new FormCollection { {"CustomerName", "Richard"} }; // Act var modelState = TryUpdateModel(viewModel, addressValues); // Assert Assert.False(modelState.IsValid); 
+8
Feb 26 '14 at 13:55
source share

I had a problem where TestsHelper worked most of the time, but not for the validation methods defined by the IValidatableObject interface. CompareAttribute also gave me some problems. This is why try / catch is there. The following code seems to confirm all cases:

 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
Feb 01 '12 at 20:25
source share



All Articles