unit testingASP.NET DataAnnotationsvalidation

我正在使用DataAnnotations进行我的模型validation即

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

在我的控制器中,我正在检查ModelState的值。 对于从我的视图发布的无效模型数据,这正确地返回false。

但是,在执行我的控制器操作的unit testing时,ModelState总是返回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); } 

在testing中,我是否需要做额外的工作来build立模型validation?

谢谢,

ModelBinder将执行validation。 在这个例子中,你自己构build了ShippingDetails ,它将跳过ModelBinder ,从而完全validation。 请注意inputvalidation和模型validation之间的区别。 inputvalidation是为了确保用户提供了一些数据,因为他有机会这样做。 如果您提供没有关联字段的表单,则不会调用关联的validation器。

MVC2在模型validation和inputvalidation方面有所变化,所以确切的行为取决于您使用的版本。 有关MVC和MVC 2的详细信息,请参阅http://bradwilson.typepad.com/blog/2010/01/input-validation-vs-model-validation-in-aspnet-mvc.html

[编辑]我想最干净的解决scheme是通过提供一个自定义模拟ValueProvider手动调用控制器上的UpdateModel 。 这应该激发validation并正确设置ModelState

我发布在我的博客文章 :

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

我正在通过http://bradwilson.typepad.com/blog/2009/04/dataannotations-and-aspnet-mvc.html ,在这篇文章中,我不喜欢将validationtesting放在控制器testing中的想法,在每个testing中手动检查是否存在validation属性。 因此,下面是帮助器方法和它的用法,它适用于EDM(具有元数据属性,因为我们无法将自动生成的EDM类应用属性的原因)以及将ValidationAttributes应用于其属性的POCO对象。

帮助器方法不parsing为分层对象,但validation可以在平坦的单个对象上进行testing(types级别)

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

这是另一个职位http://johan.driessen.se/archive/2009/11/18/testing-dataannotation-based-validation-in-asp.net-mvc.aspx谈论在;.Net 4validation,但我认为我会坚持我的帮手方法,这是有效的3.5和4

我喜欢在我的模型上testing数据属性,并在控制器的上下文之外查看模型。 我已经通过编写我自己的TryUpdateModel版本来完成这个工作,它不需要控制器,可以用来填充一个ModelState字典。

这里是我的TryUpdateModel方法(主要来自.NET MVC控制器源代码):

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

这可以很容易地用于像这样的unit testing:

 // Arrange var viewModel = new AddressViewModel(); var addressValues = new FormCollection { {"CustomerName", "Richard"} }; // Act var modelState = TryUpdateModel(viewModel, addressValues); // Assert Assert.False(modelState.IsValid); 

我有一个问题,TestsHelper大部分时间工作,但不是由IValidatableObject接口定义的validation方法。 CompareAttribute也给了我一些问题。 这就是为什么try / catch在那里。 以下代码似乎validation所有情况:

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