Domanda

Come posso verificare che l'azione del mio controller stia inserendo gli errori corretti nel ModelState durante la convalida di un'entità, quando utilizzo la convalida DataAnnotation in MVC 2 Preview 1?

Qualche codice per illustrare.Innanzitutto l'azione:

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

Ed ecco un test unitario fallito che penso dovrebbe essere superato ma non lo è (usando MbUnit e Moq):

[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);
}

Immagino che oltre a questa domanda, Dovrebbe Sto testando la convalida e dovrei testarla in questo modo?

È stato utile?

Soluzione

Invece di passare in un BlogPost si può anche dichiarare il parametro di azioni come FormCollection. Quindi è possibile creare il BlogPost te stesso e chiamare UpdateModel(model, formCollection.ToValueProvider());.

Questo attiverà la convalida per qualsiasi campo del 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);
    }

Basta assicurarsi che il test aggiunge un valore nullo per ogni campo il punto di vista forma che si desidera lasciare vuota.

ho scoperto che facendo in questo modo, a scapito di poche righe in più di codice, rende il mio test di unità somigliano il modo in cui il codice viene chiamato in fase di esecuzione più da vicino che li rende più prezioso. Inoltre è possibile verificare che cosa accade quando qualcuno entra "abc" in un controllo associato a una proprietà int.

Altri suggerimenti

Odio Necro un vecchio post, ma ho pensato di aggiungere i miei pensieri (dato che ho appena avuto questo problema e sono imbattuto in questo post mentre cerca la risposta).

  1. Non testare la convalida nei test del controller. O si fidi di convalida del MVC o lascia la tua (vale a dire non si prova il codice di altre, provare il codice)
  2. Se si vuole testare la convalida sta facendo quello che ci si aspetta, testarlo nei test modello (faccio questo per un paio di miei più complesse convalide regex).

Quello che veramente desidera verificare è che il controller fa quello che ci si aspetta che faccia quando la validazione fallisce. Questo è il tuo codice, e le vostre aspettative. Testing è facile una volta che ti rendi conto che è tutto quello che desidera di prova:

[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));
}

I aveva avuto lo stesso problema, e dopo aver letto Pauls risposta e commentare, ho cercato un modo di convalidare manualmente il modello di vista.

Ho trovato questo che tutorial spiega come convalidare manualmente un ViewModel che utilizza DataAnnotations. Essi frammento di codice Key è verso la fine del post.

I modificato il codice un po '- nel tutorial 4 ° parametro del TryValidateObject viene omesso (validateAllProperties). Al fine di ottenere tutte le annotazioni per convalidare, questo dovrebbe essere impostata su true.

Additionaly ho riscritta il codice in un metodo generico, per fare il test di validazione ViewModel semplice:

    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);
        }
    }

Finora questo ha funzionato molto bene per noi.

Quando si chiama il metodo homeController.Index nel test, non si sta utilizzando uno qualsiasi dei framework MVC che spara la validazione in modo ModelState.IsValid sarà sempre vero. Nel nostro codice che noi chiamiamo un metodo di supporto Convalida direttamente nel controller piuttosto che utilizzare la convalida ambiente. Non ho avuto molta esperienza con i DataAnnotations (Usiamo NHibernate.Validators) forse qualcun altro in grado di offrire una guida come chiamare Convalida dal di dentro il controller.

ero alla ricerca di questo oggi e ho trovato questo post del blog da Roberto Hernández (MVP), che sembra fornire la migliore soluzione per sparare i validatori per un'azione di controllo durante il test di unità. Questo metterà gli errori corretti nel ModelState durante la convalida un'entità.

sto usando ModelBinders nei miei casi di test per essere in grado di aggiornare il valore model.IsValid.

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

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

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

Con il mio metodo MvcModelBinder.BindModel come segue (in pratica lo stesso codice usato internamente nel 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);
        }

Questa non è esattamente rispondere alla tua domanda, perché abbandona DataAnnotations, ma io inserirlo perché potrebbe aiutare altre persone scrivono i test per i loro controllori:

Hai la possibilità di non utilizzare la convalida fornito da System.ComponentModel.DataAnnotations ma ancora utilizzando l'oggetto ViewData.ModelState, utilizzando il suo metodo AddModelError e qualche altro meccanismo di convalida. Per esempio:

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);
    }
}

In questo modo, ancora di sfruttare la roba Html.ValidationMessageFor() che MVC genera, senza utilizzare il DataAnnotations. È necessario assicurarsi che la chiave utilizzata con AddModelError partite ciò che la vista si aspetta per i messaggi di convalida.

Il controller diventa quindi verificabili perché la convalida sta avvenendo in modo esplicito, invece di essere fatto automagicamente dal framework MVC.

Sono d'accordo che ARM ha la migliore risposta:. Testare il comportamento del controller, non è il built-in di convalida

Tuttavia, è anche possibile test di unità che il vostro modello / ViewModel ha gli attributi di validazione corretti definito. Diciamo che il tuo ViewModel si presenta così:

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

Questo test unità verificare l'esistenza dell'attributo [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);
}

A differenza di ARM, non ho un problema con la tomba di scavo. Così qui è il mio suggerimento. Esso si basa sulla risposta di Giles Smith e lavora per ASP.NET MVC4 (so che la questione è di circa MVC 2, ma Google non fa discriminazioni quando si cerca di risposte e non riesco a provare sul MVC2.) Invece di mettere il codice di convalida in un metodo statico generico, ho messo in un controller di test. Il controller è dotato di tutto il necessario per la convalida. Quindi, il controller di test è simile al seguente:

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);
            }
        }
    }

Naturalmente la classe non ha bisogno di essere una classe interna protetta, che è il modo che uso ora ma probabilmente intenzione di riutilizzare quella classe. Se da qualche parte c'è un modello MyModel che è decorato con bei attributi dati di annotazione, allora il test simile a questa:

    [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.");
    }

Il vantaggio di questa configurazione è che posso riutilizzare il controller di test per le prove di tutti i miei modelli e può essere in grado di estenderlo a prendere in giro un po 'di più sul controller o utilizzare i metodi protetti che un controllore ha.

Speranza che aiuta.

Se vi preoccupate per la convalida, ma non si cura di come viene implementato, se ti interessa solo sulla convalida del metodo di azione al più alto livello di astrazione, non importa se si è implementato come utilizzando DataAnnotations, ModelBinders o anche ActionFilterAttributes, allora si potrebbe utilizzare il pacchetto NuGet Xania.AspNet.Simulator come segue:

install-package Xania.AspNet.Simulator

-

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

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

In base a @ Giles-smith risposte e commenti, per l'API Web 's:

    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);
        }
    }

Pubblicare sul risposta di immissione sopra ...

La risposta di @ Giles-smith è il mio approccio preferito, ma l'implementazione può essere semplificata:

    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);
        }
    }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top