If you have developed asp.net mvc applications you will probably be familiar with the following code:
[HttpPost]
public ActionResult DoSomething(SomeModel model)
{
if (ModelState.IsValid)
{
// do something then redirect
return RedirectToAction("Index");
}
return View(model)
}
If our input model is valid then we typically perform some action with it, then redirect (following the GET, POST, GET principle).
If it’s not valid, then we return to the current view passing the posted model.
Since most of my logic is abstracted into other services, I don’t tend to test my controllers that often. However, in case you do, you would want to test the type of action result based on the input model.
Typically we use the MvcContrib.TestHelper library for this. However just creating an “invalid” model and passing it to your controller action isn’t enough if you are using DataAnnotation based validation. This is because in a normal request, the validation is invoked by the appropriate ModelBinder. If you are calling your actions directly in your tests, then no Model Binding occurs, and therefore your ModelState will always be valid.
As the assertion I want to make is quite simple, I found the simplest approach is to first validate my model, then manually set ModelState before testing my result (much easier than mocking ModelBindingContext).
To test the DataAnnotations I wrote the following helper (you’ll need a reference to System.ComponentModel.DataAnnotations):
public static bool ValidateModel(object model) {
var validationResults = new List<ValidationResult>();
var ctx = new ValidationContext(model, null, null);
return Validator.TryValidateObject(model, ctx, validationResults, true);
}
We can then test our controller actions like so:
[Test]
public void Register_with_valid_post_returns_redirect()
{
var model = new AccountRegisterModel { Email = "a@b.com", Password = "foobar", Url = "ben.onfabrik.com" };
TestHelper.ValidateModel(model).ShouldBeTrue();
accountController.Register(model).AssertActionRedirect();
}
[Test]
public void Register_with_invalid_post_returns_view()
{
var model = new AccountRegisterModel();
TestHelper.ValidateModel(model).ShouldBeFalse();
// invalidate modelstate
accountController.ModelState.AddModelError("", "");
accountController.Register(model)
.AssertViewRendered().ViewData.ModelState.IsValid.ShouldBeFalse();
}
I’m not advocating testing every controller action, in fact, one could argue that if you feel the need to test your controllers then perhaps you have too much logic within them. Definitely check out “Put your controllers on a diet” by Jimmy Bogard - http://tekpub.com/conferences/mvcconf.
In fact after writing the above code, I abstracted most of my logic into handlers (see Jimmy’s video) and ditched the tests, but knowledge is power and sharing is caring so there you go.