Programing

단위 테스트 ASP.NET DataAnnotations 유효성 검사

lottogame 2020. 11. 17. 07:39
반응형

단위 테스트 ASP.NET DataAnnotations 유효성 검사


내 모델 유효성 검사를 위해 DataAnnotations를 사용하고 있습니다.

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

내 컨트롤러에서 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);
    }

테스트에서 모델 유효성 검사를 설정하기 위해 추가 작업이 필요합니까?

감사,


에서 유효성 검사를 수행합니다 ModelBinder. 이 예에서는 사용자 ShippingDetails자신 을 구성 ModelBinder하므로 유효성 검사를 완전히 건너 뜁니다 . 입력 유효성 검사와 모델 유효성 검사의 차이점에 유의하십시오. 입력 유효성 검사는 사용자가 데이터를 제공 할 수있는 기회를 제공했는지 확인하는 것입니다. 연결된 필드없이 양식을 제공하면 연결된 유효성 검사기가 호출되지 않습니다.

모델 유효성 검사와 입력 유효성 검사에서 MVC2가 변경되었으므로 정확한 동작은 사용중인 버전에 따라 다릅니다. MVC 및 MVC 2에 대한 자세한 내용 http://bradwilson.typepad.com/blog/2010/01/input-validation-vs-model-validation-in-aspnet-mvc.html참조하십시오 .

[편집] 이것에 대한 가장 깨끗한 해결책 UpdateModel은 사용자 정의 mock을 제공하여 테스트 할 때 컨트롤러를 수동으로 호출 하는 것 ValueProvider입니다. 유효성 검사를 실행하고 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을 거치고 있었는데 ,이 게시물에서는 유효성 검사 테스트를 컨트롤러 테스트에 넣는 아이디어가 마음에 들지 않았습니다. 유효성 검사 속성이 있는지 여부를 각 테스트에서 수동으로 확인합니다. 따라서 아래는 도우미 메서드와 제가 구현 한 사용법입니다. 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);
    }

}

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


I like to test the data attributes on my models and view models outside the context of the controller. I've done this by writing my own version of TryUpdateModel that doesn't need a controller and can be used to populate a ModelState dictionary.

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

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 a unit test like this:

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

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

// Assert
Assert.False(modelState.IsValid);

I had an issue where TestsHelper worked most of the time but not for validation methods defined by the IValidatableObject interface. The CompareAttribute also gave me some problems. That is why the try/catch is in there. The following code seems to validate 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);
        }
    }
}

참고URL : https://stackoverflow.com/questions/2167811/unit-testing-asp-net-dataannotations-validation

반응형