Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing a controller in ASP.NET MVC 2 with RedirectToAction

I have a controller that implements a simple Add operation on an entity and redirects to the Details page:

[HttpPost]
public ActionResult Add(Thing thing)
{ 
    // ... do validation, db stuff ...
    return this.RedirectToAction<c => c.Details(thing.Id));
}

This works great (using the RedirectToAction from the MvcContrib assembly).

When I'm unit testing this method I want to access the ViewData that is returned from the Details action (so I can get the newly inserted thing's primary key and prove it is now in the database).

The test has:

var result = controller.Add(thing);

But result here is of type: System.Web.Mvc.RedirectToRouteResult (which is a System.Web.Mvc.ActionResult). It doesn't hasn't yet executed the Details method.

I've tried calling ExecuteResult on the returned object passing in a mocked up ControllerContext but the framework wasn't happy with the lack of detail in the mocked object.

I could try filling in the details, etc, etc but then my test code is way longer than the code I'm testing and I feel I need unit tests for the unit tests!

Am I missing something in the testing philosophy? How do I test this action when I can't get at its returned state?

like image 598
Rob Walker Avatar asked Mar 13 '10 22:03

Rob Walker


3 Answers

I am using MVC2 RC2 at the moment and the answer from rmacfie didn't quite work for me but did get me on the right track.

Rightly or wrongly I managed to do this in my test instead:

var actionResult = (RedirectToRouteResult)logonController.ForgotUsername(model);

actionResult.RouteValues["action"].should_be_equal_to("Index");
actionResult.RouteValues["controller"].should_be_equal_to("Logon");

Not sure if this will help someone but might save you 10 minutes.

like image 104
ArtificialGold Avatar answered Oct 14 '22 18:10

ArtificialGold


There is MVC Contrib TestHelper that are fantastic for testing most of the ActionResult

You can get it here: http://mvccontrib.codeplex.com/wikipage?title=TestHelper

Here is an example of the syntax:

var controller = new TestController();

controller.Add(thing)
          .AssertActionRedirect()
          .ToAction<TestController>(x => x.Index());

To test if the data has been persisted successfully, you should maybe ask your database directly, I don't know if you're using an ORM or something, but you should do something to get the last insterted item in your database, then compare with the value you provided to your Add ActionResult and see if this is ok.

I don't think that testing your Details ActionResult to see if your data is persisted is the right approach. That would not be an unit test, more a functional test.

But you should also unit test your Details method to make sure that your viewdata object is filled with the right data coming from your database.

like image 32
Charles Ouellet Avatar answered Oct 14 '22 20:10

Charles Ouellet


You seem to be doing way too much for a unit test. The validation and data access would typically be done by services that you call from the controller action. You mock those services and only test that they were called properly.

Something like this (using approximate syntax for Rhino.Mocks & NUnit):

[Test]
public void Add_SavesThingToDB()
{
    var dbMock = MockRepository.GenerateMock<DBService>();
    dbMock.Expect(x => x.Save(thing)).Repeat.Once();

    var controller = new MyController(dbMock);
    controller.Add(new Thing());

    dbMock.VerifyAllExpectations();
}

[Test]
public void Add_RedirectsAfterSave()
{
    var dbMock = MockRepository.GenerateMock<DBService>();

    var controller = new MyController(dbMock);
    var result = (RedirectToRouteResult)controller.Add(new Thing());

    Assert.That(result.Url, Is.EqualTo("/mynew/url"));
}
like image 7
rmac Avatar answered Oct 14 '22 18:10

rmac