Test d’unité ASP.NET DataAnnotations validation

J’utilise DataAnnotations pour la validation de mon modèle

[Required(ErrorMessage="Please enter a name")] public ssortingng Name { get; set; } 

Dans mon contrôleur, je vérifie la valeur de ModelState. Cela renvoie correctement false pour les données de modèle non valides publiées à partir de ma vue.

Cependant, lors de l’exécution du test unitaire de l’action de mon contrôleur, ModelState renvoie toujours 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(ssortingng.IsNullOrEmpty(result.ViewName)); Assert.IsFalse(result.ViewData.ModelState.IsValid); } 

Dois-je faire quelque chose en plus pour configurer la validation du modèle dans mes tests?

Merci,

Ben

La validation sera effectuée par le ModelBinder . Dans l’exemple, vous construisez vous-même les ShippingDetails , qui sauteront le ModelBinder et donc la validation entièrement. Notez la différence entre la validation des entrées et la validation du modèle. La validation des entrées permet de s’assurer que l’utilisateur a fourni certaines données, étant donné qu’il a eu la chance de le faire. Si vous fournissez un formulaire sans le champ associé, le validateur associé ne sera pas appelé.

Il y a eu des changements dans MVC2 sur la validation du modèle par rapport à la validation des entrées, donc le comportement exact dépend de la version que vous utilisez. Voir http://bradwilson.typepad.com/blog/2010/01/input-validation-vs-model-validation-in-aspnet-mvc.html pour plus de détails à ce sujet concernant MVC et MVC 2.

[EDIT] Je suppose que la solution la plus propre consiste à appeler UpdateModel sur le contrôleur manuellement lors du test en fournissant un simulacre de ValueProvider personnalisé. Cela devrait déclencher la validation et définir le ModelState correctement.

J’ai posté ceci sur mon blog :

 // model class using System.ComponentModel.DataAnnotations; namespace MvcApplication2.Models { public class Fiz { [Required] public ssortingng Name { get; set; } [Required] [RegularExpression(".+@..+")] public ssortingng 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 ValidateModel(object model) { var validationResults = new List(); var ctx = new ValidationContext(model, null, null); Validator.TryValidateObject(model, ctx, validationResults, true); return validationResults; } 

Je passais en revue http://bradwilson.typepad.com/blog/2009/04/dataannotations-and-aspnet-mvc.html , dans cet article je n’aimais pas l’idée de mettre les tests de validation dans le test du contrôleur et quelque peu vérification manuelle dans chaque test que si l’atsortingbut de validation existe ou non. Donc, voici la méthode d’assistance et son utilisation que j’ai implémentée, cela fonctionne à la fois pour EDM (qui possède des atsortingbuts de métadonnées, car nous ne pouvons pas appliquer les atsortingbuts sur les classes EDM générées automatiquement) et les objects POCO qui ont des ValidationAtsortingbutes appliqués à leurs propriétés .

La méthode d’assistance n’parsing pas les objects hiérarchiques, mais la validation peut être testée sur des objects individuels plats (type-niveau)

 class TestsHelper { internal static void ValidateObject(T obj) { var type = typeof(T); var meta = type.GetCustomAtsortingbutes(false).OfType().FirstOrDefault(); if (meta != null) { type = meta.MetadataClassType; } var propertyInfo = type.GetProperties(); foreach (var info in propertyInfo) { var atsortingbutes = info.GetCustomAtsortingbutes(false).OfType(); foreach (var atsortingbute in atsortingbutes) { var objPropInfo = obj.GetType().GetProperty(info.Name); atsortingbute.Validate(objPropInfo.GetValue(obj, null), info.Name); } } } } ///  /// Link EDM class with meta data class ///  [MetadataType(typeof(ServiceMetadata))] public partial class Service { } ///  /// Meta data class to hold validation atsortingbutes for each property ///  public class ServiceMetadata { ///  /// Name ///  [Required] [SsortingngLength(1000)] public object Name { get; set; } ///  /// Description ///  [Required] [SsortingngLength(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 = ssortingng.Empty}; TestsHelper.ValidateObject(serv); } } 

Ceci est un autre article http://johan.driessen.se/archive/2009/11/18/testing-dataannotation-based-validation-in-asp.net-mvc.aspx qui parle de la validation dans .Net 4, mais je pense que je vais m’en tenir à ma méthode d’assistance qui est valable à la fois dans 3.5 et 4

J’aime tester les atsortingbuts de données sur mes modèles et voir les modèles en dehors du contexte du contrôleur. Je l’ai fait en écrivant ma propre version de TryUpdateModel qui n’a pas besoin de contrôleur et peut être utilisée pour remplir un dictionnaire ModelState.

Voici ma méthode TryUpdateModel (principalement prise à partir du code source de .NET MVC Controller):

 private static ModelStateDictionary TryUpdateModel(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; } 

Cela peut ensuite être facilement utilisé dans un test unitaire comme celui-ci:

 // Arrange var viewModel = new AddressViewModel(); var addressValues = new FormCollection { {"CustomerName", "Richard"} }; // Act var modelState = TryUpdateModel(viewModel, addressValues); // Assert Assert.False(modelState.IsValid); 

J’ai eu un problème où TestsHelper fonctionnait la plupart du temps, mais pas pour les méthodes de validation définies par l’interface IValidatableObject. Le CompareAtsortingbute m’a également posé des problèmes. C’est pourquoi le try / catch est là. Le code suivant semble valider tous les cas:

 public static void ValidateUsingReflection(T obj, Controller controller) { ValidationContext validationContext = new ValidationContext(obj, null, null); Type type = typeof(T); MetadataTypeAtsortingbute meta = type.GetCustomAtsortingbutes(false).OfType().FirstOrDefault(); if (meta != null) { type = meta.MetadataClassType; } PropertyInfo[] propertyInfo = type.GetProperties(); foreach (PropertyInfo info in propertyInfo) { IEnumerable atsortingbutes = info.GetCustomAtsortingbutes(false).OfType(); foreach (ValidationAtsortingbute atsortingbute in atsortingbutes) { PropertyInfo objPropInfo = obj.GetType().GetProperty(info.Name); try { validationContext.DisplayName = info.Name; atsortingbute.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 results = valObj.Validate(validationContext); foreach (ValidationResult result in results) { ssortingng key = result.MemberNames.FirstOrDefault() ?? ssortingng.Empty; controller.ModelState.AddModelError(key, result.ErrorMessage); } } }