MonoRail Applications: Testability Expectations

time to read 9 min | 1650 words

Scott's has suggested that I post about the different approaches to testability in MonoRail vs. other MVC implementations. Specifically, this was caused by Scott's post about MonoRail, and a concern he raised about MonoRail and testability:

I haven't seen much Model-View-Presenter framework stuff in MonoRail, but I might have missed it.  I'd prefer to not have to loose the testability that comes with the pattern

At first, I really couldn't see how he got to this conclution, but after a short email discussion, I think that I can see how he got this idea. And this is likely to confuse other people who looks into MonoRail. The confusing issue here is that many people has gotten use to a an active view approach. Let us take the following example and see how we should build it using classic ASP.Net MVC and using MonoRail.

You have form with a text box and a button. When the button is pressed the textbox and button are hidden and the text "Hello "+ whateverInTheTextBox appears.

Can't get any simpler than that, now can it?

Using ASP.Net MVC, you would probably have something like this view;

public class HelloUserView : Page, IHelloUserView
{
  IHelloUserController controller;
  public HelloUserView ()
  {
     controller = new HelloUserController(this);
  } 
  protected void Button_Click(object sender, EventArgs e0
  {
    controller.ButtonPressed();
  }
  // more stuff
}

And this controller:

public class HelloUserController : IHelloUserController
{
  IHelloUserView view;
  public HelloUserController (IHelloUserView view)
  {
    this.view = view;
  }
  public void ButtonPressed()
  {
    view.HideTextBoxAndButton();
    view.DisplayText("Hello, "+ view.TextFromUser);
  }
}

Testing this is fairly simple, you mock the view and assert that the correct calls are made.

MockRepository mocks = new MockRepository ();
IHelloUserView view = mocks.CreateView<IHelloUserView>();

//set expectations
Expect.Call(view.TextFromUser0.Return("ayende");
view.HideTextBoxAndButton();
view.DislayText("Hello, ayende");
mocks.ReplayAll();

HelloUserController controllerUnderTest = new HelloUserController(view);
controllerUnderTest.ButtonPressed();

mocks.VerifyAll();

This is how most of the MVC implementation are working, the view is active in that it takes part in the process (even if only by exposing events).

Now, let us try this again, using MonoRail, here it the initial view:

<form action="/helloUser/displayGreeting.rails">

  Your name: <input type="text" name="userName">

</form>

And the controller is:

public class HelloUserController : Controller
{
  public void Index() { } // just display the view

  public void DisplayGreeting(string userName)
  {
    PropertyBag["userName"] = userName;
  }
}

And the DisplayGreeting view is:

Hello, ${userName}

What just happened? The views in MR are strictly output mechanism alone. Unlike Win/WebForms, where the view needs to inform its controller about the various events.The framework is responsible for handing stuff to the controller. Actually, thinking about it, is is not quite right. The view is responsible for setting things up so a user action will cause the appropriate things in the controller. This is a somewhat more passive role, but it seems to me that it is still doing its job.

When it comes to testing the controller, there isn't a need to mock the view, you can directly verify that the controller is creating the appropriate entries in the PropertyBag/Flash, which the view will use. When it comes to the test the interaction between the view and the user, it is important to note that the view doesn't do such a thing at all. As you can see, there isn't a responsability here that isn't being tested, the view just doesn't play this part.

In the above example, we are relying on the framework to handle a lot of things that we had to deal with ourselves previously. Here is how we can test the functionality:

HelloUserController controllerUnderTest = new HelloUserController ();
controllerUnderTest.DisplayGreeting("ayende");
Assert.AreEquals("ayende", controllerUnderTest.PropertyBag["userName"]);

The view have setup the output so the action of the user would cause the response on the controller. It is a quite different paradigm when you look at it from a conceptual level, but very simple to grasp when you just look at what is going on.