Welcome to the CodeSmith Community!

MVC2 Unit Testing, Populating ModelState

CodeSmith Community

A description has not yet been added to this group.

MVC2 Unit Testing, Populating ModelState

Rate This
  • Comments 6

I love how testable ASP.NET MVC is, I also love MVC2's model validation. However when trying to unit test a controller method that used the ModelState, I quickly learned that the ModelState is not populated when just newing up a Controller and calling one of its public methods. As usual, I think this is best narrated by example:

Example Model and Controller

public class PersonModel
{
  [Required]
  public string Name { get; set; }
}

public class PersonController : Controller
{
  [AcceptVerbs(HttpVerbs.Get)]
  public ViewResult Register(Person person)
  {
    return View(new PersonModel());
  }
  [AcceptVerbs(HttpVerbs.Post)]
  public ViewResult Register(Person person)
  {
    if (!ModelState.IsValid)
      return View(model);
    PersonService.Register(person);
    return View("success");
  }
}

Example of the Problem

[Test]
public void RegisterTest()
{
  var model = new PersonModel { Name = String.Empty }; // This is model is invalid.
  var controller = new PersonController();
  var result = controller.Register(model);
  // This fails because the ModelState was valid, although the passed in model was not.
  Assert.AreNotEqual("success", result.ViewName);
}

Solution

Other solutions I have come across were adding the errors to the model state manually, or mocking the ControllerContext as to enable the Controller's private ValidateModel method. I didn't like the former because it felt like I wasn't actually testing the model validation, and I didn't like the latter because it seemed like a lot of work to both mocking things and then still have to manually expose a private method.

My solution is (I feel) pretty simple: Add an extension method to the ModelStateDictionary that allows you to pass in a model, and it will then validate that model and add it's errors to the dictionary.

public static void AddValidationErrors(this ModelStateDictionary modelState, object model)
{
  var context = new ValidationContext(model, null, null);
  var results = new List<ValidationResult>();
  Validator.TryValidateObject(model, context, results, true);
  foreach (var result in results)
  {
    var name = result.MemberNames.First();
    modelState.AddModelError(name, result.ErrorMessage);
  }
}

Example of the Solution

[Test]
public void RegisterTest()
{
  var model = new PersonModel { Name = String.Empty }; // This is model is invalid.
  var controller = new PersonController();
  // This populates the ModelState errors, causing the Register method to fail and the unit test to pass.
  controller.ModelState.AddValidationErrors(model);
  var result = controller.Register(model);
  Assert.AreNotEqual("success", result.ViewName);
}

kick it on DotNetKicks.com

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • * Please enter your name
  • * Please enter a comment
  • Post
  • You've been kicked (a good thing) - Trackback from DotNetKicks.com

  • Thank you for submitting this cool story - Trackback from DotNetShoutout

  • When doing separate validation in tests you do not actually test the real implementation.

    You would better use TryUpdateModel instead of passing the object. It will perform the validation.

    Regards,

    Dima.

  • Dima,

    TryUpdateModel is a private method inside of the controller, and so it can't be called from the unit test itself. Also, using it requires you to mock the ControllerContext. I felt the solution offered in this blog post, while certainly not perfect, was relatively quick and simple way to enhance my MV2 unit tests so that they include some simple validation logic.

    Thanks,

    Tom

  • Great idea but, how would you do this in MVC 2 .Net 3.5?

  • Hello Craig,

    I believe that this code was compiled against MVC2 and .NET 3.5. Are you running into an issue with this code? Could you please create a new forum post with additional information.

    Thanks

    -Blake Niemyjski

Page 1 of 1 (6 items)
Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • * Please enter your name
  • * Please enter a comment
  • Post