2011-03-18 102 views
24

我正在使用DataAnnotations的RegularExpressionAttribute進行驗證,並且想要測試我的正則表達式。有沒有辦法在單元測試中直接調用屬性?如何調用驗證屬性進行測試?

我希望能夠做一些類似的:在單元測試

public class Person 
{ 
    [RegularExpression(@"^[0-9]{3}-[0-9]{3}-[0-9]{4}$")] 
    public string PhoneNumber { get; set; } 
} 

然後:

[TestMethod] 
public void PhoneNumberIsValid 
{ 
    var dude = new Person(); 
    dude.PhoneNumber = "555-867-5309"; 

    Assert.IsTrue(dude.IsValid); 
} 

甚至

Assert.IsTrue(dude.PhoneNumber.IsValid); 

回答

21

我最終使用了DataAnnotations命名空間中的靜態Validator類。我的測試,現在看起來是這樣的:

[TestMethod] 
public void PhoneNumberIsValid() 
{ 
    var dude = new Person(); 
    dude.PhoneNumber = "666-978-6410"; 

    var result = Validator.TryValidateObject(dude, new ValidationContext(dude, null, null), null, true); 

    Assert.IsTrue(result); 
} 
+3

這個解決方案的問題在於它測試你在該屬性上的所有驗證器,而不僅僅是正則表達式,所以你沒有很好地隔離你的測試用例。你有沒有考慮將Regex存儲在全局可訪問的地方,無論是在資源文件還是在Web配置?由於您只是想在此時驗證您的正則表達式,因此您可以使用Regex.Match()單獨測試該模式。 – joelmdev 2013-09-24 13:46:25

+2

由於你只是想驗證一個屬性,你可能想考慮['Validator.TryValidateProperty'](https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.validator。 validateproperty%28v = vs.110%29.aspx)方法。這是假設你的Person類實際上不僅僅是一個電話號碼。 – Erik 2016-03-18 12:48:34

+0

好東西兄弟,謝謝! – 2018-01-23 17:08:07

7

就新了RegularExpressionAttribute對象。

var regularExpressionAttribute = new RegularExpressionAttribute("pattern"); 

Assert.IsTrue(regularExpressionAttribute.IsValid(objToTest)); 
+0

這可能是我必須去的方式,但是我會在測試中需要我的模式副本。我想避免這種情況。我將以我希望能夠做的事爲例來編輯帖子。 – CobraGeek 2011-03-23 20:39:22

+0

@CobraGeek我用一個靜態常量文件解決了這個問題,見下文 – 2014-07-08 15:04:17

1

我用@馬丁的建議與這讓我避免specifing正則表達式字符串本地

[TestMethod] 
public void Test_Regex_NationalinsuranceNumber() 
{ 
    var regularExpressionAttribute = new RegularExpressionAttribute(Constants.Regex_NationalInsuranceNumber_Validate); 

    List<string> validNINumbers = new List<string>() { "TN311258F", "QQ123456A" }; 
    List<string> invalidNINumbers = new List<string>() { "cake", "1234", "TS184LZ" }; 
    validNINumbers.ForEach(p => Assert.IsTrue(regularExpressionAttribute.IsValid(p))); 
    invalidNINumbers.ForEach(p => Assert.IsFalse(regularExpressionAttribute.IsValid(p))); 
} 
2

對不起,回答晚了靜態常量文件一起。

我是新來的。如果你想在隔離測試每個ValidationAttribute你可以繼續到下一個方式,例如:

[Test] 
    public void Test_the_State_value_IsRequired() 
    { 
     string value = "Finished"; 
     var propertyInfo = typeof(TimeoffTemporalIncapacityEntry).GetProperty("State"); 
     var attribute = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), true).Cast<RequiredAttribute>().FirstOrDefault(); 
     Assert.IsTrue(attribute.IsValid(value)); 
    } 
1

你可以使用這個類來驗證任何ValidationAttribute類型分離:包含屬性 T =類類型, A =鍵入ValidationAttribute

例子:

string stateValue = "Pendiente"; 
ValidationAttributeValidator<ConfirmationTemporalIncapacityEntry, RequiredAttribute> validator = 
    new ValidationAttributeValidator<ConfirmationTemporalIncapacityEntry, RequiredAttribute>(); 
Assert.IsTrue(validator.ValidateValidationAttribute("State", stateValue)); 


public class ValidationAttributeValidator<T,A> 
{ 
    public ValidationAttributeValidator() { } 

    public bool ValidateValidationAttribute(string property, object value) 
    { 
     var propertyInfo = typeof(T).GetProperty(property); 
     var validationAttributes = propertyInfo.GetCustomAttributes(true); 

     if (validationAttributes == null) 
     { 
      return false; 
     } 
     List<ValidationAttribute> validationAttributeList = new List<ValidationAttribute>(); 
     foreach (object attribute in validationAttributes) 
     { 
      if (attribute.GetType() == typeof(A)) 
      { 
       validationAttributeList.Add((ValidationAttribute)attribute); 
      } 
     } 
     return(validationAttributeList.Exists(x => x.IsValid(value))); 
    } 
} 
0

大廈@ Evelio的答案,我要提供的答案,因爲這一點,你的單元測試自定義驗證似乎怎麼不就是藝術在互聯網上的任何地方都可以使用,這是搜索如何操作時遇到的頂級命中之一。

@ Evelio的答案非常接近,但它可以做更多的解釋。

要測試您的驗證,您需要有一個將驗證屬性附加到其成員數據的類。在這裏,我使用了一個新的自定義驗證器,它對於我的稱爲FeeTimeUnitValidator的項目非常有用。這個驗證器將範圍和另一個屬性作爲輸入。如果其他屬性爲零,則驗證程序所附屬的屬性無關緊要。但是如果其他屬性不爲零,則該屬性需要在該範圍內。 這裏是MockClass我用來測試:

class MockClass 
    { 
     public decimal Fee { get; set; } 

     [FeeTimeUnitValidator(otherPropertyName:"Fee", minValue:1, maxValue:12)] 
     public int attributeUnderTest { get; set; } 

     public int badOtherProperty { get; set; } 
     [FeeTimeUnitValidator(otherPropertyName: "badOtherProperty", minValue: 1, maxValue: 12)] 
     public int badAttributeUnderTest { get; set; } 

     [FeeTimeUnitValidator(otherPropertyName: "NotFoundAttribute", minValue: 1, maxValue: 12)] 
     public int nameNotFoundAttribute { get; set; } 
    } 

通知屬性驗證:

[FeeTimeUnitValidator(otherPropertyName:"Fee", minValue:1, maxValue:12)] 

這是說,以檢查屬性「費」的收費性質(即,它必須是無-zero),然後範圍是1-12。

我在單元測試類中實例化類並使用setup方法進行設置。由於這個類有三個屬性具有驗證器,因此我將屬性的名稱傳入設置類。

private MockClass classUnderTest; 
    private ValidationContext context; 

    FeeTimeUnitValidator setup(string attributeUnderTest) 
    { 
     classUnderTest = new MockClass(); 
     classUnderTest.Fee = 0; 
     var propertyInfo = typeof(MockClass).GetProperty(attributeUnderTest); 
     var validatorArray = propertyInfo.GetCustomAttributes(typeof(FeeTimeUnitValidator), true); 

     Assert.AreEqual(1, validatorArray.Length); 
     var validator = validatorArray[0]; 
     Assert.IsTrue(validator.GetType().Equals(typeof(FeeTimeUnitValidator))); 

     context = new ValidationContext(classUnderTest, null, null); 

     return (FeeTimeUnitValidator)validator; 
    } 

有一些有趣的事情。我正在使用@ Evelio的方法從屬性中提取驗證器。這是安裝程序的第3行和第4行。然後,因爲這是一個單元測試方法,所以我做了一些斷言,以確保我得到了我所期望的。當我將這個模式轉移到另一個單元測試類以用於另一個驗證器時,這實際上遇到了問題。然後另一個關鍵是我創建了ValidationContext(因爲更復雜的驗證器需要一個上下文來查找它們引用的其他屬性 - 在我的情況下,我使用它來查找Fee屬性)。當我正在研究如何對這些自定義驗證器進行單元測試時,有什麼令我惱火的是ValidationContext。我找不到有關如何創建它們的任何信息。我相信屬性驗證的「上下文」是屬性所在的類。這就是爲什麼我使用類實例作爲第一個參數創建驗證上下文的原因。這然後爲驗證器提供對類的其他屬性的訪問,以便您可以進行跨屬性驗證。

現在,我已經創建的背景和一個指向驗證,我可以跳進單元測試本身,以確保驗證是否正確履行其職責:

[TestMethod] 
    public void TestInRangeIsValidWhenFeeNonZero() 
    { 
     // Arrange 
     var validator = setup("attributeUnderTest"); 
     classUnderTest.Fee = 10; 

     // Act 
     ValidationResult value12 = validator.GetValidationResult(12, context); 
     ValidationResult value1 = validator.GetValidationResult(1, context); 
     ValidationResult value5 = validator.GetValidationResult(5, context); 

     // Assert 
     Assert.AreEqual(ValidationResult.Success, value12); 
     Assert.AreEqual(ValidationResult.Success, value1); 
     Assert.AreEqual(ValidationResult.Success, value5); 
    } 

如果我的驗證沒有需要一個上下文(即它可以在不引用其他屬性的情況下驗證該屬性),那麼我可以使用IsValid()的更簡單的接口,但是如果驗證器需要非null上下文,則必須使用GetValidationResult()像我在這裏完成的方法。

我希望這可以幫助別人誰可能會寫驗證器,並像我一樣對宗教單元測試。 :)

Here is a good article on creating custom validators.

0
// You can do something like this. 
[TestMethod] 
public void PhoneNumberIsValid 
{ 
    var propInfo = typeof(Person).GetProperty("PhoneNumber"); 
    var attr = propInfo.GetCustomAttributes(typeof(RegularExpressionAttribute), true); 

    // Act Assert Positives 
     Assert.IsTrue(((RegularExpressionAttribute)attr [0]).IsValid("555-55-5555")); 

     // Act Assert Negative 
     Assert.IsFalse(((RegularExpressionAttribute)attr[0]).IsValid("123654654654")); 

     } 
0

擴展上@ CobraGeek答案,並@ Erik的評論,你可以使用Validator.TryValidateProperty驗證只有一個字段而不是整個的對象,像這樣:

var results= new List<ValidationResult>(); 

Person dude = new Person(); 

System.ComponentModel.TypeDescriptor.AddProviderTransparent 
(new AssociatedMetadataTypeTypeDescriptionProvider(dude.GetType()), dude.GetType()); 

dude.PhoneNumber = "555-867-5309"; 

var vc = new ValidationContext(dude, null, null); 

vc.MemberName = "PhoneNumber"; 

bool result = Validator.TryValidateProperty(dude.PhoneNumber, vc, results); 

之後result是表示驗證成功的布爾值,並且如果假results包含所拋出錯誤的詳細信息列表。