Over a million developers have joined DZone.

ASP.NET MVC Unit Testing with UpdateModel and TryUpdateModel

·

Last week I was in the middle of re-writing an old MVC application. The app was written during the early days of MVC, and as a consequence it was a playground for learning and experimentation. As a developer, I am continuously learning new tricks so its’ no surprise that when I look at the code I wrote a few years ago, I generally will feel the urge to refactor it. For example, when I started out with ASP.NET MVC I did not fully understand how the model binders worked. Unfortunately, this meant that some of my controller actions ended up looking like this:

[HttpPost]
public ActionResult Edit( Guid id, FormCollection form )
{
var foo = new SomeModel();
foo.Id = id;
foo.Name = form["Name"];
foo.Date = DateTime.Parse(form["EffectiveDate"]);
foo.Description = form["Description"];
....

if (ModelState.IsValid)
{
...Save and redirect
}

return View(model);
}

Yuck! Referencing form collections in your controller are a major no no! The problem with using a form collection is that it is not strongly typed and it is prone to errors. Its easy to make a typo or get your controller action out of sync with your view. Therefore it is preferred when you can bind directly to a view model or utilize the UpdateModel or TryUpdateModel methods to parse the form. For example the code above can be rewritten as:

[HttpPost]
public ActionResult Edit( Guid id, foo SomeModel )
{
UpdateModel( foo );

if (ModelState.IsValid)
{
...Save and redirect
}

return View(model);
}

As you can clearly see, the implementation is cleaner and requires much less maintenance by the developer. Anyways, as I continued on my quest to refactor old code, I made it a point to add some unit tests so I could ensure that my refactoring efforts did not break any existing functionality:

[Test]
public void Edit_Post_Action_Updates_Model_And_Redirects()
{
//Arrange
var repository = new FakeRepository();
var foo = new SomeModel() { Id = 1, Name = "Test", Date = DateTime.Parse("1/1/2010"), Description = "Bar" };
_repository.Add( foo);

var model = new SomeViewModel(_repository, foo.Id);
var controller = new MyController(_repository);

//Act
var result = controller.Edit(foo.Id, model) as RedirectToRouteResult;

//Assert
Assert.IsNotNull(result);
Assert.AreEqual( result.RouteValues["action"], "Index" );

var updated = repository.SingleOrDefault( x => x.Id == 1 );
Assert.IsNotNull(updated);
Assert.AreEqual(updated.Name, "Test" );
Assert.AreEqual(updated.Date, DateTime.Parse("1/1/2010") );
Assert.AreEqual(updated.Description, "Bar" );
}

Unfortunately when I ran the unit test, the following error was thrown during the call to UpdateModel.

System.NullReferenceException : Object reference not set to an instance of an object.

Luckily, I have run into this problem before so I knew how to fix it. However, I know that not everyone has leveraged Unit Testing in MVC before. So I thought I would document the steps that are required to successfully test a method that utilizes TryUpdateModel or UpdateModel:

  1. You need to fake out the controller context. This can be easily done my adding a reference to Moq and taking advantage of the MvcMockHelpers created by Scott Hanselman. The helper class is used to fake out things like the HttpContext. The magic is all secretly contained in the SetFakeControllerContext extension method.
  2. You need to fake out the form values. Why, because at runtime, the model binder associates form values with model properties via reflection. In other words, if you have a textbox with named “FirstName”, a call to UpdateModel will automatically associate it with the model’s property of the same name. This is the exactly the same as what we did in the first example, when we explicitly set the model.FirstName = form[“FirstName”].

Here is the updated unit test that runs successfully:

[Test]
public void Edit_Post_Action_Updates_Model_And_Redirects()
{
//Arrange
var repository = new FakeRepository();
var foo = new SomeModel() { Id = 1, Name = "Foo", Date = DateTime.Parse("1/1/1999"), Description = "Bar" };
_repository.Add( foo);

var model = new SomeViewModel(_repository, foo.Id);
var controller = new MyController(_repository);
controller.SetFakeControllerContext();

//Act
var result = controller.Edit(foo.Id, model) as RedirectToRouteResult;
controller.SetFakeControllerContext();

var fakeForm = new FormCollection();
fakeForm.Add("Name", "Hello");
fakeForm.Add("Date", "1/1/2010");
fakeForm.Add("Description", "World" );
controller.ValueProvider = fakeForm.ToValueProvider();

//Assert
Assert.IsNotNull(result);
Assert.AreEqual( result.RouteValues["action"], "Index" );

var updated = repository.SingleOrDefault( x => x.Id == 1 );
Assert.IsNotNull(updated);
Assert.AreEqual(updated.Name, "Hello" );
Assert.AreEqual(updated.Date, DateTime.Parse("1/1/2010") );
Assert.AreEqual(updated.Description, "World" );
}

In conclusion, try to avoid using form collections and take advantage of the model binding that MVC has provided us with. If you are not unit testing your controllers then you should start immediately. Although unit testing may cost you some extra work up front, you should see a return on investment due to a reduced bug rate. In addition, when you have good test coverage you will rest assured at night knowing that your code is stable and in proper working order.

Topics:

Published at DZone with permission of Michael Ceranski, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}