Frage

Wie kann ich feststellen, dass meine Controller-Aktion, die richtigen Fehler in dem Model setzt, wenn ein Unternehmen der Validierung, wenn ich verwende DataAnnotation Validierung in MVC 2 Preview 1?

Einige Code zu veranschaulichen. Zunächst wird die Aktion:

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

Und hier ist ein versagende Unit-Test, dass ich denke geben werden sollte, aber nicht (mit MbUnit & 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);
}

ich zusätzlich zu dieser Frage erraten, sollte I-Tests Validierung werden, und sollte ich es auf diese Weise testen?

War es hilfreich?

Lösung

Statt in einem BlogPost zugeben können Sie auch die Aktionen Parameter als FormCollection erklären. Dann können Sie die BlogPost selbst erstellen und UpdateModel(model, formCollection.ToValueProvider()); nennen.

Dies wird die Validierung für jedes Feld in den FormCollection auslösen.

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

So stellen Sie sicher, dass Ihr Test für jedes Feld in den Ansichten, die einen Nullwert addiert bilden, dass Sie leer verlassen wollen.

Ich fand, dass es auf diese Weise zu tun, auf Kosten von ein paar zusätzliche Zeilen Code, meine Unit-Tests macht den Weg der Code zur Laufzeit ähneln sie wertvoller enger machen aufgerufen wird. Auch können Sie testen, was passiert, wenn jemand „abc“ in einem Steuerelement gebunden int Eigenschaft eintritt.

Andere Tipps

Hass eine alte Post Necro, aber ich dachte, dass ich meine eigenen Gedanken hinzufügen würde (da ich dieses Problem nur hatte und über diesen Beitrag lief, während die Antwort sucht).

  1. Sie nicht testen Validierung in Ihrem Controller-Tests. Entweder Sie MVC Validierung vertrauen oder schreiben Sie Ihre eigenen (das heißt nicht testen anderen Code, testen Sie Ihren Code)
  2. Wenn Sie nicht möchten Validierung testen tut, was Sie erwarten, testen Sie es in Ihrem Modellversuche (Ich tue dies für ein paar meiner komplexeren regex Validierungen).

Was wollen Sie wirklich hier testen ist, dass Ihr Controller tut, was Sie erwarten, dass es tun, wenn die Validierung fehlschlägt. Das ist der Code, und Ihre Erwartungen. Testen es einfach ist, wenn Sie das ist alles, was Sie wollen Test realisieren:

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

Ich war das heute untersucht, und ich fand diese Blog-Post von Roberto Hernández (MVP), die die beste Lösung zu bieten scheint die Validierer für eine Controller-Aktion während der Unit-Tests zu feuern. Dies wird die Korrektur von Fehlern in dem Model setzen, wenn ein Unternehmen zu validieren.

Ich bin mit Modelbinder in meinem Test-Fälle in der Lage sein model.IsValid Wert zu aktualisieren.

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

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

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

Mit meiner MvcModelBinder.BindModel Methode wie folgt (im Grunde der gleiche Code verwendet intern im MVC-Framework):

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

Das ist genau Ihre Frage nicht beantworten, weil es DataAnnotations verläßt, aber ich werde es hinzufügen, weil es andere Menschen für ihre Controller Tests schreiben helfen kann:

Sie haben die Möglichkeit, nicht die Validierung von System.ComponentModel.DataAnnotations bereitgestellt verwenden, aber immer noch das ViewData.ModelState-Objekt durch seine AddModelError Verfahren und einen anderen Validierungsmechanismus verwenden. Z. B:

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

Das nach wie vor können Sie die Vorteile der Html.ValidationMessageFor() Sachen nehmen, die MVC erzeugt, ohne die DataAnnotations zu verwenden. Sie müssen sicherstellen, dass der Schlüssel, den Sie mit AddModelError verwenden übereinstimmt, was die Sicht für die Validierung Nachrichten erwartet wird.

Die Steuerung wird dann prüfbar, weil die Validierung explizit geschieht, anstatt automatisch durch den MVC-Framework durchgeführt.

Ich bin damit einverstanden, dass ARM die beste Antwort hat. Das Verhalten des Controllers testen, nicht die integrierten Validierungs

Sie können jedoch auch Unit-Test, dass Ihr Modell / Ansichtsmodell die richtigen Validierungsattribute definiert. Lassen Sie uns sagen, dass Ihre Ansichtsmodell wie folgt aussieht:

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

Diese Einheit Test für die Existenz des [Required] Attribut testen:

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

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

    Assert.IsNotNull(attribute);
}

Im Gegensatz zu ARM, ich habe kein Problem mit Grab zu graben. So, hier ist mein Vorschlag. Es baut auf der Antwort von Giles Smith und arbeitet für ASP.NET MVC4 (Ich weiß, die Frage zu MVC 2, aber Google nicht diskriminieren, wenn die Suche nach Antworten, und ich kann nicht testen auf MVC2.) Statt die in einer allgemeinen statischen Methode Validierungscode das Setzens, ich habe es in einem Test-Controller. Der Controller verfügt über alles, was für die Validierung benötigt. So sieht der Test-Controller wie folgt aus:

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

Natürlich ist die Klasse braucht nicht geschützt zu sein Innerclass, das ist die Art, wie ich es jetzt benutzen, aber ich werde wahrscheinlich diese Klasse wiederverwenden. Wenn irgendwo ein Modell MyModel ist, die mit schönen Datenaufbelichtung Attribute eingerichtet, dann sieht der Test etwas wie folgt aus:

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

Der Vorteil dieser Einrichtung ist, dass ich die Test-Controller für Tests aller meiner Modelle wiederverwenden kann und kann in der Lage sein, es zu erweitern ein bisschen mehr über den Controller zu verspotten oder die geschützten Methoden verwenden, die ein Controller hat.

Hoffe, es hilft.

Wenn Sie die Validierung kümmern, aber sie kümmern sich nicht darum, wie sie umgesetzt wird, wenn Sie über die Validierung des Aktionsmethode auf höchstem Abstraktionsniveau sorgen, egal ob sie als mit DataAnnotations, Modelbinders implementiert wird oder sogar ActionFilterAttributes, dann könnten Sie Xania.AspNet.Simulator nuget Paket verwenden, wie folgt:

install-package Xania.AspNet.Simulator

-

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

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

Basierend auf @ giles-smith ‚s Antwort und Kommentare, für Web-API:

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

Siehe auf Antwort bearbeitet oben ...

@ giles-smith Antwort ist mein bevorzugter Ansatz, aber die Implementierung vereinfacht werden kann:

    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);
        }
    }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top