While reading this article, please keep in mind that I am a novice in using Presenter First. The following technique has not been fully tested in a production environment, and may have drawbacks that I have not anticipated.
In developing an obscenely large and complicated personal project, I have become a big fan of the Presenter First design pattern. It ‘clicks’ with me in a way that standard Model-View-Controller and Model-View-Presenter patterns simply have not.
A quick explanation of what makes Presenter First a little different: A Presenter contains a reference to a Model interface and a View interface. The Presenter subscribes to events on the Model and View. The Model and View can only communicate with the Presenter by raising these events. The Model and View do not have any direct reference to each other. This allows you to test the Model and Presenter logic without creating a View.
I highly recommend reading the Presenter First white paper (PDF) for more details.
In my project, I’ve been trying to follow a test-driven development approach, where I write test cases (using NUnit and NMock2) before I write the actual code.
I immediately ran into issues testing event subscription and event firing. It was difficult to test the presenter, because it required the test to fire an event owned by the mock Model.
For example, this was my presenter code for a simple History presenter. The goal is the History model fires the MessageLogged event when a new message has been added to the History log, which will prompt the Presenter to call the View’s UpdateHistory function.
public interface IHistoryModel
{
event Action MessageLogged;
string Message { get; }
}
public interface IHistoryView
{
void UpdateHistory( string aMessage );
}
public class HistoryPresenter
{
private readonly IHistoryModel mModel;
private readonly IHistoryView mView;
public HistoryPresenter( IHistoryModel aModel,
IHistoryView aView )
{
mModel = aModel;
mView = aView;
aModel.MessageLogged += OnMessageLogged;
}
private void OnMessageLogged()
{
mView.UpdateHistory( mModel.Message );
}
}
To test this, I mocked up a Model and View with NMock2, but NMock2 does not have any direct provisions for firing events that are on mock objects. I googled around, found a way to create MockEvents, and I found myself with a terrible, complicated test case.
To be fair, the Presenter First white paper does recommend that events should not be specified in the interface itself; they should be hidden by a separate function, like ‘SubscribeMessageLogged’. I found this concept ridiculous, but I started to understand why.
After struggling with MockEvents for several days, I had a sudden epiphany that would greatly simplify everything. I changed the Model event to provide the associated data as a parameter (a behavior that was also discouraged by the white paper) and transform the Presenter into a brain-dead connector.
public interface IHistoryModel
{
event Action<string> MessageLogged;
}
public interface IHistoryView
{
void UpdateHistory( string aMessage );
}
public class HistoryPresenter
{
public HistoryPresenter( IHistoryModel aModel,
IHistoryView aView )
{
aModel.MessageLogged += aView.UpdateHistory;
}
}
And… that’s it. The Presenter doesn’t even need any additional methods outside of the constructor!
The test case is super simple as well:
[TestFixture]
public class HistoryPresenterTest
{
[Test]
public void Constructor()
{
var lMockery = new Mockery();
var lView = lMockery.NewMock<IHistoryView>();
var lModel = lMockery.NewMock<IHistoryModel>();
Expect.Once.On( lModel ).EventAdd(
"MessageLogged",
new Action(
lView.UpdateHistory ) );
new HistoryPresenter( lModel, lView );
lMockery.VerifyAllExpectationsHaveBeenMet();
}
}
The mock Model expects that it will be connected to the View’s UpdateHistory method, and that’s all of the testing that is required. There is no need to raise the MessageLogged event, because that would simply test the functionality of C# events.
The advantage of this simple presenter is even more profound as you add new events to the Model and View. Now, all that is needed is one event, one method, one line connecting the model and view in the Presenter, and one line in the test case.
As long as each event can be tied to a corresponding method, this makes writing presenters (and their tests) extremely simple.
Simple Presenter Example Source Code (26 KB, requires Visual Studio 2008 and Nunit 2.4.8)