Menus

Tuesday, December 4, 2012

MVC2 Unit Testing, Populating ModelState

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

No comments:

Post a Comment