14 May
2010

Input Validierung im Unit Test mit ASP.NET MVC 2

 

Mit ASP.NET MVC 2 ist es ja nun möglich über DataAnnotations oder mit eigenen ModelValidatoren eine schnelle und einfache Validierung der Eingabedaten vorzunehmen. Dieses im Unit Test zu machen ist jedoch out of the Box nicht möglich.

Thomas Bandt hat dazu mal einen Blog Beitrag geschrieben. Der sich jedoch rein auf DataAnnotations bezieht und keine anderen ModelValdator berücksichtigt. Dies ist natürlich nicht ganz so gut. Desweiteren funktioniert sie nur unter .NET 4 und man kann damit nicht den Programmfluss im Controller selbst testen (was macht dieser bei invaliden Daten?).

Hier stelle ich eine Extension-Method vor die ohne diese Schwachpunkte auskommt.

public static class ControllerValidationExtensions
 {
     public static void ValidateModel(this Controller controller, object model)
     {
         ValidateModel(controller, model, null);
     }

     public static void ValidateModel(this Controller controller, object model, string prefix)
     {
         if (model == null)
         {
             throw new ArgumentNullException("model");
         }
         EnsureControllerContext(controller);
         foreach (ModelValidationResult validator in model.GetModelValidator(controller).Validate(model))
         {
             controller.AddModelError(prefix, validator);
         }
     }

     private static void EnsureControllerContext(Controller controller)
     {
         if (controller.ControllerContext==null)
         {
             controller.ControllerContext=new ControllerContext();
         }
     }

     private static ModelValidator GetModelValidator(this object model, Controller controller)
     {
         return ModelValidator.GetModelValidator(
             ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
             controller.ControllerContext
             );
     }

     private static void AddModelError(this Controller controller, string prefix, ModelValidationResult validator)
     {
         controller.ModelState.AddModelError(
             CreateSubPropertyName(prefix, validator.MemberName),
             validator.Message);
     }

     private static string CreateSubPropertyName(string prefix, string propertyName)
     {
         if (string.IsNullOrEmpty(prefix))
         {
             return propertyName;
         }
         if (string.IsNullOrEmpty(propertyName))
         {
             return prefix;
         }
         return (prefix + "." + propertyName);
     }
 }

Ruft man dies nun im Testlauf vor dem Aufruf der Controller-Action auf, so ist auch der ModelState mit den entsprechenden Fehlern gefüllt. Auch werden die im ASP.NET MVC registrierten MetaDataProvider und ModelValidatoren dafür verwenden. Eine eventuelle Registrierung dieser muss natürlich im Test erfolgen.

Nun ein kleines, einfaches Beispiel.

Gegeben ist folgendes ViewModel

public class UserRegistrationModel
{
    [Required]
    public string Username { get; set; }

    [Required]
    public string EMail { get; set; }

    [Required]
    public string Password { get; set; }

    [Required]
    public string PasswordConfirmation { get; set; }
}

und dieser Controller, der bewusst einfach gehalten ist, damit schneller erfasst werden kann.

public class UserRegistrationController : RegularlyController
{
    private readonly IUserRegistration userRegistration;
    private readonly IRegularlyContext regularlyContext;

    public UserRegistrationController(IUserRegistration userRegistration, IRegularlyContext regularlyContext)
    {
        this.userRegistration = userRegistration;
        this.regularlyContext = regularlyContext;
    }

    public ActionResult Index()
    {
        return View(new UserRegistrationModel());
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Index(UserRegistrationModel model)
    {
        if (ModelState.IsValid)
        {
            /* magie */
            return RedirectToAction("ThankYou");
        }
        return View(model);
    }
}

Der Ablauf eines typischen Test wäre dann folgender

[Fact]
public void SimpleValidationTest()
{
    var controller = new UserRegistrationController(null,null);
    var postModel = new UserRegistrationModel
    {
        EMail = "info@der-albert.com",
        Password = "password",
        PasswordConfirmation = "password"
    };

    controller.ValidateModel(postModel);
    var result = controller.Index(postModel);

    result.ShouldBeAnInstanceOf<ViewResult>();
}

Die Validierung muss manuell durchgeführt werden bevor die Action aufgerufen wird. Dies passiert im Framework auch davor. Bei einem Test ohne die Validierung würde immer ein RedirectRouteResult zurückgegeben.

Viel Spaß damit!


Der Eintrag ist mir etwas Wert
 

Feedback

# Updates zu ASP.NET MVC und DataAnnotations

left by Thomas goes .NET at 5/15/2010 11:05 AM Gravatar
Ich habe mich zwar schon vor einiger Zeit von der Verwendung der DataAnnotations in meinen ernsthaften Projekten wieder verabschiedet, doch habe ich just diese Woche erst wieder ein kleineres Projekt damit umgesetzt (man glaubt ja gar nicht wie effiz ...
Comments have been closed on this topic.