Reputation: 593
I am using the RegularExpressionAttribute from DataAnnotations for validation and would like to test my regex. Is there a way to invoke the attribute directly in a unit test?
I would like to be able to do something similar to this:
public class Person
{
[RegularExpression(@"^[0-9]{3}-[0-9]{3}-[0-9]{4}$")]
public string PhoneNumber { get; set; }
}
Then in a unit test:
[TestMethod]
public void PhoneNumberIsValid
{
var dude = new Person();
dude.PhoneNumber = "555-867-5309";
Assert.IsTrue(dude.IsValid);
}
Or even
Assert.IsTrue(dude.PhoneNumber.IsValid);
Upvotes: 28
Views: 16926
Reputation: 3064
Extending on @CobraGeek's answer and @Erik's comment, you can use the Validator.TryValidateProperty
to validate only that one field instead of the whole object, as so:
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);
After which result
is the boolean indicating success of the validation, and if false results
contains the list of the details of the errors thrown.
Upvotes: 1
Reputation: 188
// 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"));
}
Upvotes: 0
Reputation: 1890
Building on @Evelio's answer I am going to provide an answer to how do you unit test custom validators since this doesn't seem to be articulated anywhere on the internet and this is one of the top hits that come up when searching for how to do it.
@Evelio's answer is very close, but it could do with a bit more of an explanation.
To test your validation you need to have a class that attaches validation attributes to its member data. Here I am using a new custom validator that makes sense for my project called FeeTimeUnitValidator. This validator takes a range and another attribute as input. If the other attribute is zero, then the attribute the validator is attached to doesn't matter. But if the other attribute is not zero, then this attribute needs to be in the range. Here is the MockClass I use for testing:
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; }
}
Notice the attribute validation:
[FeeTimeUnitValidator(otherPropertyName:"Fee", minValue:1, maxValue:12)]
This says to check the property "Fee" as the Fee property (i.e., it has to be non-zero) and then the range is 1 - 12.
I instantiate class in the unit test class and set it up with a setup method. Since there are three attributes on this class that have the validator, I pass in the name of the attribute into the setup class.
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;
}
There are a few things of interest. I am using @Evelio's approach to extract the validator from the attribute. This is doe in lines 3 and 4 of the setup routine. Then, since this is a unit test method, I do some asserts to make sure that I got what I expected. This actually caught a problem when I transferred this pattern to another unit test class for another validator.
Then the other key is that I create the ValidationContext (since the more complicated validators need a context to find the other attributes they refer to - in my case I use it to find the Fee attribute). When I was researching how to unit test these custom validators, what was tripping me up was the ValidationContext. I couldn't find any information about how to create them. I believe the "context" for the attribute validation is the class in which the attribute lives. This is why I create the validation context with the class instance as the first parameter. This then provides the validator with access to the other attributes on the class so you can do cross attribute validation.
Now that i have the context created and a pointer to a validator, I can jump into the unit test itself to ensure that the validator is doing its job properly:
[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);
}
If my validator didn't need a context (i.e., it could validate the attribute without reference to the other attributes), then I could use the simpler interface of IsValid(), but if the validator needs a non-null context, you have to use the GetValidationResult() method like I have done here.
I hope this helps somebody else who might be writing validators and is as religious about unit testing as I am. :)
Here is a good article on creating custom validators.
Upvotes: 1
Reputation: 23
You can use this class for validate any ValidationAttribute type in isolate: T = class type containing the property, A = type ValidationAttribute
Example:
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)));
}
}
Upvotes: 1
Reputation: 21
Sorry for answering late.
I'm new here. If you want test every ValidationAttribute in isolate you can proceed to the next manner for example:
[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));
}
Upvotes: 2
Reputation: 474
I used the @Martin 's suggestion along with a static constants file which allowed me to avoid specifing the regex string locally
[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)));
}
Upvotes: 1
Reputation: 593
I ended up using the static Validator class from the DataAnnotations namespace. My test now looks like this:
[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);
}
Upvotes: 29
Reputation: 11041
Just new up a RegularExpressionAttribute object.
var regularExpressionAttribute = new RegularExpressionAttribute("pattern");
Assert.IsTrue(regularExpressionAttribute.IsValid(objToTest));
Upvotes: 6