Question

Comment puis-je vérifier que mon action de contrôleur met les corriger les erreurs dans le ModelState lors de la validation d'une entité, lorsque j'utilise la validation de DataAnnotation dans MVC 2 Preview 1?

code pour illustrer. Tout d'abord, l'action:

    [HttpPost]
    public ActionResult Index(BlogPost b)
    {
        if(ModelState.IsValid)
        {
            _blogService.Insert(b);
            return(View("Success", b));
        }
        return View(b);
    }

Et voici un test unitaire à défaut, je pense qu'il faut passer, mais n'est pas (en utilisant MbUnit & QMC):

[Test]
public void When_processing_invalid_post_HomeControllerModelState_should_have_at_least_one_error()
{
    // arrange
    var mockRepository = new Mock<IBlogPostSVC>();
    var homeController = new HomeController(mockRepository.Object);

    // act
    var p = new BlogPost { Title = "test" };            // date and content should be required
    homeController.Index(p);

    // assert
    Assert.IsTrue(!homeController.ModelState.IsValid);
}

Je suppose que, en plus de cette question, devrait -je validation d'essais, et dois-je tester de cette façon?

Était-ce utile?

La solution

Au lieu de passer dans un BlogPost vous pouvez également déclarer le paramètre actions comme FormCollection. Ensuite, vous pouvez créer le BlogPost vous et appelez UpdateModel(model, formCollection.ToValueProvider());.

Cela déclenchera la validation pour un champ dans le FormCollection.

    [HttpPost]
    public ActionResult Index(FormCollection form)
    {
        var b = new BlogPost();
        TryUpdateModel(model, form.ToValueProvider());

        if (ModelState.IsValid)
        {
            _blogService.Insert(b);
            return (View("Success", b));
        }
        return View(b);
    }

Assurez-vous que votre test ajoute une valeur nulle pour tous les domaines de la vue formulaire que vous voulez laisser vide.

Je trouve que cette façon de faire, au détriment de quelques lignes de code supplémentaires, fait mes tests unitaires ressemblent à la façon dont le code est appelé à l'exécution de plus près ce qui les rend plus précieux. vous pouvez également tester ce qui se passe quand quelqu'un entre dans « abc » dans un contrôle lié à une propriété int.

Autres conseils

Je déteste Necro un ancien poste, mais je pensais que j'ajouter mes propres pensées (depuis que je viens d'avoir ce problème et couru à travers ce poste tout en cherchant la réponse).

  1. Ne pas tester la validation dans vos tests de contrôleur. Soit vous faites confiance à la validation de MVC ou écrivez votre propre (à savoir ne pas tester autre code de, testez votre code)
  2. Si vous voulez tester la validation est en train de faire ce que vous attendez, testez-le dans vos tests de modèle (je le fais pour un couple de mes regex plus validations complexes).

Qu'est-ce que vous voulez vraiment tester ici est que votre contrôleur fait ce que vous attendez qu'il fasse lorsque la validation échoue. C'est votre code et vos attentes. L'essai, il est facile une fois que vous vous rendez compte que tout est que vous voulez tester:

[test]
public void TestInvalidPostBehavior()
{
    // arrange
    var mockRepository = new Mock<IBlogPostSVC>();
    var homeController = new HomeController(mockRepository.Object);
    var p = new BlogPost();

    homeController.ViewData.ModelState.AddModelError("Key", "ErrorMessage"); // Values of these two strings don't matter.  
    // What I'm doing is setting up the situation: my controller is receiving an invalid model.

    // act
    var result = (ViewResult) homeController.Index(p);

    // assert
    result.ForView("Index")
    Assert.That(result.ViewData.Model, Is.EqualTo(p));
}

Je recherchais aujourd'hui et j'ai trouvé ce blog par Roberto Hernández (MVP) qui semble fournir la meilleure solution pour tirer les validateurs pour une action de contrôleur lors de tests unitaires. Cela mettra les corriger les erreurs dans le ModelState lors de la validation d'une entité.

J'utilise ModelBinders dans mes cas de test pour être en mesure de mettre à jour la valeur model.IsValid.

var form = new FormCollection();
form.Add("Name", "0123456789012345678901234567890123456789");

var model = MvcModelBinder.BindModel<AddItemModel>(controller, form);

ViewResult result = (ViewResult)controller.Add(model);

Avec ma méthode de MvcModelBinder.BindModel comme suit (essentiellement le même code utilisé en interne dans le framework MVC):

        public static TModel BindModel<TModel>(Controller controller, IValueProvider valueProvider) where TModel : class
        {
            IModelBinder binder = ModelBinders.Binders.GetBinder(typeof(TModel));
            ModelBindingContext bindingContext = new ModelBindingContext()
            {
                FallbackToEmptyPrefix = true,
                ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(TModel)),
                ModelName = "NotUsedButNotNull",
                ModelState = controller.ModelState,
                PropertyFilter = (name => { return true; }),
                ValueProvider = valueProvider
            };

            return (TModel)binder.BindModel(controller.ControllerContext, bindingContext);
        }

Cela ne répond pas exactement à votre question, car il abandonne DataAnnotations, mais je vais l'ajouter parce que cela pourrait aider d'autres personnes passent des tests pour leurs contrôleurs:

Vous avez la possibilité de ne pas utiliser la validation fournie par System.ComponentModel.DataAnnotations mais toujours en utilisant l'objet ViewData.ModelState, en utilisant sa méthode AddModelError et un autre mécanisme de validation. Par exemple:

public ActionResult Create(CompetitionEntry competitionEntry)
{        
    if (competitionEntry.Email == null)
        ViewData.ModelState.AddModelError("CompetitionEntry.Email", "Please enter your e-mail");

    if (ModelState.IsValid)
    {
       // insert code to save data here...
       // ...

       return Redirect("/");
    }
    else
    {
        // return with errors
        var viewModel = new CompetitionEntryViewModel();
        // insert code to populate viewmodel here ...
        // ...


        return View(viewModel);
    }
}

vous laisse toujours profiter des trucs de Html.ValidationMessageFor() que MVC génère, sans utiliser le DataAnnotations. Vous devez vous assurer que la clé que vous utilisez avec AddModelError correspond à ce que la vue attend pour les messages de validation.

Le contrôleur devient alors testable car la validation se passe explicitement, plutôt que d'être fait automagiquement par le framework MVC.

Je suis d'accord que ARM a la meilleure réponse. Tester le comportement de votre contrôleur, et non la validation intégrée

Cependant, vous pouvez également tester l'unité que votre modèle / ViewModel possède les attributs de validation correcte définie. Disons que votre ViewModel ressemble à ceci:

public class PersonViewModel
{
    [Required]
    public string FirstName { get; set; }
}

Ce test unitaire pour tester l'existence de l'attribut [Required]:

[TestMethod]
public void FirstName_should_be_required()
{
    var propertyInfo = typeof(PersonViewModel).GetProperty("FirstName");

    var attribute = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), false)
                                .FirstOrDefault();

    Assert.IsNotNull(attribute);
}

Contrairement à ARM, je n'ai pas de problème avec creuser la tombe. Voici donc ma suggestion. Il se fonde sur la réponse de Giles Smith et travaille pour ASP.NET MVC4 (je sais que la question est sur le MVC 2, mais Google ne fait aucune discrimination lors de la recherche de réponses et je ne peux pas tester sur MVC2.) Au lieu de mettre le code de validation dans une méthode statique générique, je l'ai mis dans un contrôleur de test. Le contrôleur a tout ce qu'il faut pour la validation. Ainsi, le contrôleur de test ressemble à ceci:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Wbe.Mvc;

protected class TestController : Controller
    {
        public void TestValidateModel(object Model)
        {
            ValidationContext validationContext = new ValidationContext(Model, null, null);
            List<ValidationResult> validationResults = new List<ValidationResult>();
            Validator.TryValidateObject(Model, validationContext, validationResults, true);
            foreach (ValidationResult validationResult in validationResults)
            {
                this.ModelState.AddModelError(String.Join(", ", validationResult.MemberNames), validationResult.ErrorMessage);
            }
        }
    }

Bien sûr, la classe n'a pas besoin d'être un protégé classe interne, qui est la façon dont je l'utilise maintenant, mais je vais probablement réutiliser cette classe. Si quelque part il y a un MyModel modèle qui est décoré avec des attributs d'annotation de données belle, le test ressemble à ceci:

    [TestMethod()]
    public void ValidationTest()
    {
        MyModel item = new MyModel();
        item.Description = "This is a unit test";
        item.LocationId = 1;

        TestController testController = new TestController();
        testController.TestValidateModel(item);

        Assert.IsTrue(testController.ModelState.IsValid, "A valid model is recognized.");
    }

L'avantage de cette configuration est que je peux réutiliser le contrôleur de test pour les tests de tous mes modèles et peut-être en mesure d'étendre à se moquer un peu plus sur le contrôleur ou utiliser les méthodes protégées qu'un contrôleur a.

it helps.

Si vous vous souciez de validation, mais vous ne se soucient pas de la façon dont il est mis en œuvre, si vous ne vous préoccupez validation de votre méthode d'action au plus haut niveau d'abstraction, peu importe si elle est mise en œuvre en utilisant DataAnnotations, ModelBinders ou même ActionFilterAttributes, alors vous pouvez utiliser package Xania.AspNet.Simulator de NuGet comme suit:

install-package Xania.AspNet.Simulator

-

var action = new BlogController()
    .Action(c => c.Index(new BlogPost()), "POST");
var modelState = action.ValidateRequest();

modelState.IsValid.Should().BeFalse();

Basé sur @ GILES-forgeron de réponse et les commentaires, l'API Web:

    public static void ValidateViewModel<TViewModel, TController>(this TController controller, TViewModel viewModelToValidate) 
        where TController : ApiController
    {
        var validationContext = new ValidationContext(viewModelToValidate, null, null);
        var validationResults = new List<ValidationResult>();
        Validator.TryValidateObject(viewModelToValidate, validationContext, validationResults, true);
        foreach (var validationResult in validationResults)
        {
            controller.ModelState.AddModelError(validationResult.MemberNames.FirstOrDefault() ?? string.Empty, validationResult.ErrorMessage);
        }
    }

Voir sur la modification de la réponse ci-dessus ...

@ réponse de GILES-smith est mon approche préférée mais la mise en œuvre peut être simplifiée:

    public static void ValidateViewModel(this Controller controller, object viewModelToValidate)
    {
        var validationContext = new ValidationContext(viewModelToValidate, null, null);
        var validationResults = new List<ValidationResult>();
        Validator.TryValidateObject(viewModelToValidate, validationContext, validationResults, true);
        foreach (var validationResult in validationResults)
        {
            controller.ModelState.AddModelError(validationResult.MemberNames.FirstOrDefault() ?? string.Empty, validationResult.ErrorMessage);
        }
    }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top