Unit testing MonoRail controllers – Redirects

When developing with MonoRail, one of the common operations is to redirect to other controllers and actions.  Originally, I looked at the BaseControllerTester to help test, but it required a little too much knowledge of the inner workings of MonoRail for my taste.  Instead, I’ll use a common Legacy Code technique to achieve the same effect.

The first attempt

The easiest way to see if a method is testable is just to try it out.  Right now I don’t have much of an idea of what to test.  I do know that the method I want to call in the Controller base class is “Redirect”.  I don’t really know what that does underneath the covers, but I don’t much care.  What I’d like to do is create a PartialMock for the AccountController, and just make sure that the “Redirect” method is called with the correct parameters.

A side note, PartialMock is great for mocking classes (as opposed to interfaces).  I can selectively remove behavior for specific methods while leaving the other methods and behavior in place.

Here’s my first attempt at a test with AccountController getting mocked out:

[TestFixture]
public class When_authenticating_with_valid_credentials
{
private MockRepository _mocks;
private IUserRepository _userRepo;
private AccountController _acctCtlr;

[SetUp]
public void Before_each_spec()
{
_mocks = new MockRepository();

_userRepo = _mocks.CreateMock<IUserRepository>();
_acctCtlr = _mocks.PartialMock<AccountController>(_userRepo);

User user = new User();
user.Username = "tester";
user.Password = "password";

Expect.Call(_userRepo.GetUserByUsername("tester")).Return(user);
Expect.Call(() => _acctCtlr.Redirect("main", "index")).Repeat.AtLeastOnce();

_mocks.ReplayAll();
}

[Test]
public void Should_redirect_to_landing_page()
{
_acctCtlr.Login("tester", "password");

_mocks.VerifyAll();
}
}

In the Before_each_spec method, I set up the appropriate mocks and use the PartialMock method to try and mock out the AccountController.  Additionally, I set up expectations for the IUserRepository and additionally for the AccountController.

Unfortunately, the test fails, but not for the reasons I like.  I get all sorts of exceptions from deep inside the MonoRail caves.  The mocking should have worked, so what went wrong?

A little workaround

Digging deeper into the MonoRail API, I find the culprit.  Although I told Rhino Mocks to intercept the Redirect call, it will only work for virtual and abstract methods.  The Redirect method is neither.

Pulling an old trick out of the hat, I wrote quite a while ago about subclassing and overriding non-virtual methods.  This trick will work just great here.  I’ll create a seam between the AccountController and MonoRail’s SmartDispatcherController:

public class BaseController : SmartDispatcherController
{
public virtual new void Redirect(string controller, string action)
{
base.Redirect(controller, action);
}
}

Additionally, I change the AccountController to inherit from this new seam class.  Since I just wrap and delegate to the base class, production code won’t be affected in the slightest.  Since my test mocks AccountController, which has a virtual “Redirect” method, the test now fails correctly.  And now that it fails correctly, I can fill out the implementation:

public class AccountController : BaseController
{
private readonly IUserRepository _repo;

public AccountController(IUserRepository repo)
{
_repo = repo;
}

public void Login(string username, string password)
{
User user = _repo.GetUserByUsername(username);

if (user != null)
{
// check password
Redirect("main", "index");
}
}
}

Although the BaseControllerTester provides a lot of out-of-the-box testing functionality, sometimes the implementation details can leak into my tests, which is what I don’t want to happen.  I like to keep my tests coupled to frameworks as little as possible, and there’s nothing really specific to MonoRail in my tests.  The method might change, but the MVC frameworks I’ve looked at all have some kind of “redirect to some other controller and action” method.

The subclass-and-override technique provides a quick way to introduce a seam into classes that don’t offer out-of-the-box testability in the places you want.  Since this pattern doesn’t affect production code, I can feel confident testing my Controllers (or anything else similar) in this manner.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in MonoRail, Patterns, TDD, Testing. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://schambers.lostechies.com Sean Chambers

    I use a “ControllerTestBase” class in all my controller tests where I have utility methods for assertions in controllers. Here is the method I use for asserting redirects:

    protected void AssertRedirectedTo(string controller, string action, string queryStringParameters)
    {
    Assert.AreEqual(Response.RedirectedTo, string.Format(@”/{0}/{1}.rails{2}”, controller, action, queryStringParameters), “RedirectedTo was not equal”);
    }

    There may be an easier way to do this, but this has worked for me thus far.

  • http://jimmybogard.lostechies.com Jimmy Bogard

    @Sean

    Maybe I’m being paranoid, but it feels weird for MonoRail implementation details to leak into my tests. I find it more comforting to just do a partial mock of one method instead of getting a bunch of scaffolding up for a controller.

    I run into this often when I test a derived class where I don’t own the base class. I tend to treat the base class as a black box.

  • Victor Kornov

    Sorry, but I don’t get you mean by “MonoRail implementation details” here. Is that a Response.RedirectedTo prop with a simple string representing url for redirect?

    Personally, I feel like you are complicating the test by making it interaction based as opposed to simpler state based. Don’t you try to abstract too much away?

    You just need a couple of helper methods, like Sean shows, if any. If they were on a Response would it hide “implementation details” enough?

  • http://jimmybogard.lostechies.com Jimmy Bogard

    @Victor

    Yes, it is just a matter of personal taste, but I only really apply this to base classes I don’t control. I don’t control SmartDispatcherController, and I don’t want to know how its operations change its state. All I care in my test is that “Redirect” is called. That is also a method I don’t own, so I don’t want to test what it does.

    What the Redirect method does is not important. That it is being called is important. I only care about the interaction, not the final state that MonoRail sets up for me.

    In the end, if I cared what “Response.RedirectedTo” was, I would look at that. But I don’t in these tests. All I care about is “did I call Redirect with XXX controller and YYY action”.

  • Victor Kornov

    Yep, I see you point about “did I call Redirect” and its a valid one. But consider that when you use BaseControllerTest all services and context are already mocked with hand-made classes from Castle.Monorail.TestSupport. So, it’s actually MockResponse.RedirectedTo. It exposes that for your testing. Note, It’s not “inner workings” of your controller, but a public state meant just for that – testing. It’s a bit “raw” out of the box though, as you may need your own convenience helper methods. From my POV it’s good enough, so I fall back to state-based testing here.

    As an aside, it’s not good to test “inner details” but OK to test public state. And interaction testing is meant for that, object interactions, i.e. there are mo than one object, e.g. your controller. Currently, you test how you interact with yourself :)

    P.S. Could you please look into enabling e-mailing responses for comments? It’s just not convenient to go here once in a while to check discussion.

  • http://jimmybogard.lostechies.com Jimmy Bogard

    @Victor

    I’ll look into the email comments. It looks like for now all I have is comments RSS.

    I think the Controller might be a special case of state/interaction testing. Since it’s a base class I don’t own, I’d almost consider it a different object.

    I’ve also run into this where we had a different team implement our base business objects. Setting property values would do all sorts of things which we didn’t know and didn’t care about. So we just made sure we did what we were supposed to do and let the underlying object do its work.

    My benchmark here was that I had no way to make the connection between the operation “Redirect” and the resultant state “Response.RedirectedTo”. Admittedly the connection between the two seems logical, but how do I know the connection exists? I have to know the underlying base class does this.

    The way we arrived at this solution was that we instantiated our controller, called “Redirect”, and saw we got exceptions. PrepareController fixes these exceptions, but for us, that we got exceptions was enough for us to stop, subclass and redirect, and use the mocks to test the interaction between our derived class and the base class.

    I wouldn’t go as far as to recommend people against PrepareController, but it’s just my personal preference to treat base classes I don’t own as an black-box dependency WRT methods.