A Simple Example of the "Humble Dialog Box"

At the Agile Austin lunch today, we talked a bit about different ways to apply the Model View Presenter pattern with WinForms clients. I promised to put up an example of using Michael Feather’s “Humble Dialog Box” to create more testable user interface code.

The current thinking for writing automated unit tests against rich clients is a modification or variation of the classic Model View Controller (MVC) architecture. Specifically, take the view part of MVC and slice it as thin as possible so that it is only a skin around the actual UI components and make it completely passive. The controller, now called the “presenter,” is responsible for all interaction with the rest of the system. There is a pattern of symbiosis between the view and the presenter. The presenter directs the view what and when to display and the view captures and relays user events to the presenter. Check the links above for a more comprehensive explanation from the professionals.

Here’s a common scenario. You have some kind of form in your application for editing a piece of data. If the user trys to close the form and there are pending changes, put up a dialog box giving the user a chance to cancel the close operation. In this case the dialog box is the major impediment to automated testing, so we’re going to hide the message box creation behind an interface that can be mocked (or stubbed if that’s your predilection). Do the same thing for the view/presenter separation. Use the Dependency Inversion Principle to abstract the view away from the presenter and mock the view in the unit tests.




using System;

using System.Windows.Forms;

using NMock;

using NUnit.Framework;



namespace SampleCode.HumbleDialogBox

{

public interface IMessageBoxCreator

{

bool AskYesNoQuestion(string title, string message);

}



public class MessageBoxCreator : IMessageBoxCreator

{

public bool AskYesNoQuestion(string title, string message)

{

DialogResult result = MessageBox.Show(message, title, MessageBoxButtons.OKCancel);

return result == DialogResult.OK;

}

}



public interface IView

{

void Close();

bool IsDirty();

}



public class Presenter

{

public const string DIRTY_CLOSE_WARNING = “Changes are pending. ”

+ “Ok to continue, cancel to return to the edit screen.”;

public const string CLOSE_WARNING_TITLE = “Changes Pending”;



private readonly IView _view;

private readonly IMessageBoxCreator _msgBox;



//

public Presenter(IView view, IMessageBoxCreator msgBox)

{

_view = view;

_msgBox = msgBox;

}



public void Close()

{

bool canClose = true;



if (_view.IsDirty())

{

canClose = _msgBox.AskYesNoQuestion(CLOSE_WARNING_TITLE, DIRTY_CLOSE_WARNING);

}



if (canClose)

{

_view.Close();

}

}

}



[TestFixture]

public class PresenterTester

{

private DynamicMock _viewMock;

private DynamicMock _msgBoxMock;

private Presenter _presenter;



[SetUp]

public void SetUp()

{

_msgBoxMock = new DynamicMock(typeof(IMessageBoxCreator));

_viewMock = new DynamicMock(typeof(IView));

_presenter = new Presenter((IView) _viewMock.MockInstance, (IMessageBoxCreator) _msgBoxMock.MockInstance);

}





[Test]

public void CloseViewWhenViewIsNotDirty()

{

// Define the expected interaction

_msgBoxMock.ExpectNoCall(“AskYesNoQuestion”, typeof(string), typeof(string));



_viewMock.ExpectAndReturn(“IsDirty”, false);

_viewMock.Expect(“Close”);



// Perform the unit of work

_presenter.Close();



// Verify the interaction

_msgBoxMock.Verify();

_viewMock.Verify();

}





[Test]

public void CloseViewWhenViewIsDirtyAndUserRespondsOk()

{

// Define the expected interaction

_msgBoxMock.ExpectAndReturn(

“AskYesNoQuestion”,

true,

Presenter.CLOSE_WARNING_TITLE,

Presenter.DIRTY_CLOSE_WARNING);



_viewMock.ExpectAndReturn(“IsDirty”, true);

_viewMock.Expect(“Close”);



// Perform the unit of work

_presenter.Close();



// Verify the interaction

_msgBoxMock.Verify();

_viewMock.Verify();

}





[Test]

public void DoNotCloseViewWhenViewIsDirtyAndUserRespondsCancel()

{

// Define the expected interaction

_msgBoxMock.ExpectAndReturn(

“AskYesNoQuestion”,

false,

Presenter.CLOSE_WARNING_TITLE,

Presenter.DIRTY_CLOSE_WARNING);



_viewMock.ExpectAndReturn(“IsDirty”, true);

_viewMock.ExpectNoCall(“Close”);



// Perform the unit of work

_presenter.Close();



// Verify the interaction

_msgBoxMock.Verify();

_viewMock.Verify();

}

}

}







Here’s a rundown of the pieces from the example code.


  1. IMessageBoxCreator/MessageBoxCreator – An interface and wrapper class around the WinForms MessageBox class. The methods in the .NET framework for dialogs are all static, and static methods cannot be mocked.
  2. IView interface – An interface that establishes the public contract between the actual form and the presenter. I didn’t show it, but assume the actual View has a reference to the Presenter.
  3. Presenter – the Presenter class drives the IView and IMessageBoxCreator interfaces. The Presenter class is completely unaware of any of the actual user interface plumbing, i.e. not one single reference to the System.Windows.Forms namespace.

In this example I used constructor injection to attach the IMessageBoxCreator. The next example is mostly the same, but I use StructureMap instead to locate the IMessageBoxCreator and take advantage of StructureMap’s built in support for NMock.




using System;

using System.Windows.Forms;

using NMock;

using NUnit.Framework;

using StructureMap;



namespace SampleCode.HumbleDialogBox2

{

[PluginFamily("Default")]

public interface IMessageBoxCreator

{

bool AskYesNoQuestion(string title, string message);

}



[Pluggable("Default")]

public class MessageBoxCreator : IMessageBoxCreator

{

public bool AskYesNoQuestion(string title, string message)

{

DialogResult result = MessageBox.Show(message, title, MessageBoxButtons.OKCancel);

return result == DialogResult.OK;

}

}



public interface IView

{

void Close();

bool IsDirty();

}



public class Presenter

{

public const string DIRTY_CLOSE_WARNING = “Changes are pending. ”

+ “Ok to continue, cancel to return to the edit screen.”;

public const string CLOSE_WARNING_TITLE = “Changes Pending”;





private readonly IView _view;



public Presenter(IView view)

{

_view = view;

}



public void Close()

{

bool canClose = true;



if (_view.IsDirty())

{

// Get the IMessageBoxCreator out of StructureMap

IMessageBoxCreator msgBox =

(IMessageBoxCreator)

ObjectFactory.GetInstance(typeof(IMessageBoxCreator));

canClose = msgBox.AskYesNoQuestion

(CLOSE_WARNING_TITLE, DIRTY_CLOSE_WARNING);

}



if (canClose)

{

_view.Close();

}

}

}



[TestFixture]

public class PresenterTester

{

private DynamicMock _viewMock;

private IMock _msgBoxMock;

private Presenter _presenter;



[SetUp]

public void SetUp()

{

_msgBoxMock = ObjectFactory.Mock(typeof(IMessageBoxCreator));

_viewMock = new DynamicMock(typeof(IView));

_presenter = new Presenter((IView) _viewMock.MockInstance);

}



[TearDown]

public void TearDown()

{

ObjectFactory.ResetDefaults();

}



[Test]

public void CloseViewWhenViewIsNotDirty()

{

// Define the expected interaction

_msgBoxMock.ExpectNoCall(“AskYesNoQuestion”, typeof(string), typeof(string));



_viewMock.ExpectAndReturn(“IsDirty”, false);

_viewMock.Expect(“Close”);



// Perform the unit of work

_presenter.Close();



// Verify the interaction

_msgBoxMock.Verify();

_viewMock.Verify();

}





[Test]

public void CloseViewWhenViewIsDirtyAndUserRespondsOk()

{

// Define the expected interaction

_msgBoxMock.ExpectAndReturn(

“AskYesNoQuestion”,

true,

Presenter.CLOSE_WARNING_TITLE,

Presenter.DIRTY_CLOSE_WARNING);



_viewMock.ExpectAndReturn(“IsDirty”, true);

_viewMock.Expect(“Close”);



// Perform the unit of work

_presenter.Close();



// Verify the interaction

_msgBoxMock.Verify();

_viewMock.Verify();

}





[Test]

public void DoNotCloseViewWhenViewIsDirtyAndUserRespondsCancel()

{

// Define the expected interaction

_msgBoxMock.ExpectAndReturn(

“AskYesNoQuestion”,

false,

Presenter.CLOSE_WARNING_TITLE,

Presenter.DIRTY_CLOSE_WARNING);



_viewMock.ExpectAndReturn(“IsDirty”, true);

_viewMock.ExpectNoCall(“Close”);



// Perform the unit of work

_presenter.Close();



// Verify the interaction

_msgBoxMock.Verify();

_viewMock.Verify();

}

}

}






Final Thoughts




It is not impossible to write automated unit tests for rich clients, but it’s definitely difficult and time consuming. So what can you do? You can take a calculated risk and forgo writing the automated tests for the user interface. The biggest problem with that approach is that a complicated rich user interface can generate a large number of bugs and requires a lot of energy towards manual regression testing (duh). You can test a WinForms application with Luke Maxon’s most excellent NUnitForms toolkit, but user interface tests are still more work to setup and execute. A better approach is to simply make as much code as possible independent of the WinForms (or Swing, etc.) engine. I say you still have to test the actual UI forms and controls. However, if they are passive and loosely coupled from the rest of the application your NUnitForms tests can be much simpler.




I left some implementation details out of the example. I’ve used the MVP pattern pretty extensively on a couple of projects now with mostly good results. Since it’s such a hot topic and the book on best practices is literally being written as I type this, I’ll try to blog soon on some MVP suggestions and pitfalls.

About Jeremy Miller

Jeremy is the Chief Software Architect at Dovetail Software, the coolest ISV in Austin. Jeremy began his IT career writing "Shadow IT" applications to automate his engineering documentation, then wandered into software development because it looked like more fun. Jeremy is the author of the open source StructureMap tool for Dependency Injection with .Net, StoryTeller for supercharged acceptance testing in .Net, and one of the principal developers behind FubuMVC. Jeremy's thoughts on all things software can be found at The Shade Tree Developer at http://codebetter.com/jeremymiller.
This entry was posted in TDD Starter Kit. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Anon

    Should the IsDirty method be part of the View or should it be part of the Model.
    If it should be a part of View, I cannot figure out how to implement it without using the Model in the View

  • josh

    What’s your take on having a method such as “void ShowMessage(string message)” on the view itself, that way the presenter can just call _view.ShowMessage(“hello”)?  Likewise for user input, add a method “bool GetYesNoResponse(string message)” to the view?  It seems easier than dependency injecting a dialog box class into the presenter.  What are the pros/cons of this?

  • http://www.rickardnilsson.net/post/The-Humble-dialog-v2.aspx Rickard Nilsson

    …none of the referenced sources discusses the issue of communication between MVP triads…

  • Vivek

    I am newbie in StructureMap. The most difficult part that I found in StructureMap is generation of Config file. I am not able to find a single .Net example( Full Source Code) with related Config file. If you have some link please send it to me.

    my email is : VivekP@ecoaxisindia.com

  • http://mc.freezope.org/ Matthias Cavigelli

    > The methods in the .NET framework for
    > dialogs are all static, and static
    > methods cannot be mocked.

    A solution I was thinking about for this, never used though, is to pass in the delegate of MessageBox.Show. The form would invoke the delegate. This delegate could be mocked out for tests. Not a nice solution, but I guess if you just have one static methods to mock out, the delegate solution is shorter. You don’t have to create IMessageBoxCreator and its implementation. Did you ever consider anything like that?

    Many developers often start with the GUI (Win / Web) designer. Then I double click on a button and quick the handler for the event is generated and I start implementing it.
    I imagine a good way to seperate the code could be never opening the form in code view.
    The proposed Interface (IView) could be implemented in the inherited class so that I never would touch the generated class.

    Hope I wasn’t confusing enough:-)

  • idior

    the view how to tiger the _presenter.Close();