80

モデルの検証に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);
}

テストでモデル検証を設定するために何か特別なことをする必要がありますか?

ありがとう、

ベン

4

5 に答える 5

133

私はこれを私のブログ投稿に投稿しました:

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;
}
于 2010-12-02T05:23:58.870 に答える
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);
    }

}

これは、.Net 4での検証について説明している別の投稿http://johan.driessen.se/archive/2009/11/18/testing-dataannotation-based-validation-in-asp.net-mvc.aspxですが、i 3.5と4の両方で有効なヘルパーメソッドに固執するつもりだと思います

于 2010-02-03T17:36:29.737 に答える
22

検証はによって実行されますModelBinder。この例では、ShippingDetails自分自身を作成し​​ます。これにより、検証がスキップModelBinderされ、検証が完全に行われます。入力検証とモデル検証の違いに注意してください。入力検証は、ユーザーがデータを提供する機会があった場合に、ユーザーがデータを提供したことを確認することです。関連するフィールドのないフォームを提供すると、関連するバリデーターは呼び出されません。

モデル検証と入力検証でMVC2に変更が加えられたため、正確な動作は使用しているバージョンによって異なります。MVCとMVC2の両方に関する詳細については、http://bradwilson.typepad.com/blog/2010/01/input-validation-vs-model-validation-in-aspnet-mvc.htmlを参照してください。

UpdateModel[編集]これに対する最もクリーンな解決策は、カスタムモックを提供してテストするときにコントローラーを手動で呼び出すことだと思いValueProviderます。これにより、検証が実行され、ModelState正しく設定されます。

于 2010-01-30T12:24:03.873 に答える
9

モデルのデータ属性をテストし、コントローラーのコンテキスト外でモデルを表示するのが好きです。これは、コントローラーを必要とせず、ModelStateディクショナリにデータを入力するために使用できる独自のバージョンのTryUpdateModelを作成することで実現しました。

これが私の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;
}

これは、次のような単体テストで簡単に使用できます。

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

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

// Assert
Assert.False(modelState.IsValid);
于 2014-02-26T13:55:05.677 に答える
1

TestsHelperがほとんどの時間機能するが、IValidatableObjectインターフェイスで定義された検証メソッドでは機能しないという問題がありました。CompareAttributeも私にいくつかの問題を与えました。それがtry/catchがそこにある理由です。次のコードは、すべてのケースを検証しているようです。

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);
        }
    }
}
于 2012-02-01T20:25:56.150 に答える