Опираясь на ответ @ 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)]
Это говорит о том, что нужно проверять свойство "Fee" как свойство Fee (т. Е. Оно должно быть ненулевым), а затем диапазон составляет 1 - 12.
Я создаю экземпляр класса в классе модульного теста и настраиваю его с помощью метода установки. Поскольку в этом классе есть три атрибута, которые имеют валидатор, я передаю имя атрибута в класс установки.
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 (), но если валидатору нужен ненулевой контекст Вы должны использовать метод GetValidationResult (), как я сделал здесь.
Я надеюсь, что это поможет кому-то еще, кто может писать валидаторы и столь же религиозен в отношении модульного тестирования, как и я. :)
Вот хорошая статья о создании пользовательских валидаторов.