单元测试ASP.NET DataAnnotations验证

Ben*_*ter 65 asp.net asp.net-mvc data-annotations

我正在使用DataAnnotations进行模型验证,即

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

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

但是,在执行我的控制器操作的单元测试时,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);
    }
Run Code Online (Sandbox Code Playgroud)

在测试中我是否需要做额外的事情来设置模型验证?

谢谢,

Jon*_*vis 103

我在我的博文中发布了这个帖子:

using System.ComponentModel.DataAnnotations;

// model class
public 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;
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,这不会通过复杂属性的验证来递归 (2认同)
  • 在“Validator.TryValidateObject(model, ctx,validationResults, true);”中使用“true”挽救了这一天。我对单个属性进行了必需的验证和正则表达式验证。在使用“true”之前,即使第二次验证,测试也会通过,尽管它不应该通过。谢谢你的回答。 (2认同)

sco*_*pio 23

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

辅助方法不会解析为分层对象,但可以在平面单个对象上测试验证(类型级别)

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

}
Run Code Online (Sandbox Code Playgroud)

这是另一篇文章http://johan.driessen.se/archive/2009/11/18/testing-dataannotation-based-validation-in-asp.net-mvc.aspx,其中讨论了.Net 4中的验证,但我我想我会坚持使用在3.5和4中都有效的辅助方法


mne*_*syn 19

验证将由ModelBinder.执行.在这个例子中,你构建了ShippingDetails自己,它将ModelBinder完全跳过并因此完成验证.请注意输入验证和模型验证之间的区别.输入验证是为了确保用户提供了一些数据,因为他有机会这样做.如果您提供没有关联字段的表单,则不会调用关联的验证程序.

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

[编辑]我想最干净的解决方案是UpdateModel在测试时手动调用Controller,提供自定义模拟ValueProvider.这应该激活验证并ModelState正确设置.


Ric*_*ide 8

我喜欢在模型上测试数据属性,并在控制器的上下文之外查看模型.我通过编写自己的TryUpdateModel版本完成了这项工作,该版本不需要控制器,可以用来填充ModelState字典.

这是我的TryUpdateModel方法(主要取自.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;
}
Run Code Online (Sandbox Code Playgroud)

这可以很容易地在这样的单元测试中使用:

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

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

// Assert
Assert.False(modelState.IsValid);
Run Code Online (Sandbox Code Playgroud)