Модульное тестирование ASP.NET проверка достоверности данных
Я использую 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);
}
нужно ли мне делать что-нибудь дополнительно, чтобы настроить проверку модели в моих тестах?
спасибо,
Бен
5 ответов:
проверка будет выполнена
ModelBinder. В этом примере вы создаетеShippingDetailsсебя, который пропуститModelBinderи таким образом, полностью проверка. Обратите внимание на разницу между входной проверкой и проверкой модели. Проверка ввода заключается в том, чтобы убедиться, что пользователь предоставил некоторые данные, учитывая, что у него была возможность сделать это. Если вы предоставляете форму без связанного поля, связанный валидатор не будет вызван.произошли изменения в MVC2 при проверке модели против проверки ввода, поэтому точное поведение зависит от версии, которую вы используете. См.http://bradwilson.typepad.com/blog/2010/01/input-validation-vs-model-validation-in-aspnet-mvc.html для получения подробной информации об этом в отношении MVC и MVC 2.
[EDIT] я думаю, что самое чистое решение для этого-позвонить
UpdateModelна контроллере вручную при тестировании, предоставляя пользовательский макет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 классы) и объекты POCO, которые имеют ValidationAttributes, применяемые к их свойствам.
вспомогательный метод не разбирается на иерархические объекты, но проверка может быть проверена на плоских отдельных объектах (на уровне типа)
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); } }это еще один пост http://johan.driessen.se/archive/2009/11/18/testing-dataannotation-based-validation-in-asp.net-mvc.aspx что говорит о проверке в .Net 4, но я думаю, что буду придерживаться моего вспомогательного метода, который действует как в 3.5, так и в 4
мне нравится тестировать атрибуты данных на моих моделях и просматривать модели вне контекста контроллера. Я сделал это, написав свою собственную версию TryUpdateModel, которая не нуждается в контроллере и может использоваться для заполнения словаря ModelState.
вот мой метод 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);
У меня была проблема, когда 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); } } }
Comments