Jeremy D. Miller -- The Shade Tree Developer

Sponsors

The Lounge

Syndication

News

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
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.


Posted Wed, Jul 20 2005 5:03 PM by Jeremy D. Miller
Filed under:

[Advertisement]

Comments

Jeremy D. Miller -- The Shade Tree Developer wrote TDD Design Starter Kit
on Thu, Jul 21 2005 1:05 PM
Here's a handful of articles on designing with or for TDD I had originally posted on my...
idior wrote re: A Simple Example of the "Humble Dialog Box"
on Wed, Jul 27 2005 6:12 AM
the view how to tiger the _presenter.Close();
Matthias Cavigelli wrote re: A Simple Example of the "Humble Dialog Box"
on Mon, Aug 15 2005 4:53 PM
> 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:-)
Jeremy D. Miller -- The Shade Tree Developer wrote The Dependency Injection Pattern – What is it and why do I care?
on Thu, Oct 6 2005 11:58 AM
A couple of weeks ago I wrote about using the Inversion of Control (IoC) principle to create classes...
Jeremy D. Miller -- The Shade Tree Developer wrote Haacked on TDD and Jeremy's First Rule of TDD
on Fri, Oct 21 2005 12:02 AM
Unit Testing Loves Beta Testing And Vice Versa
Phil Haack has a great post up in response to some folks...
Tewfik Zeghmi wrote A better design for UI code
on Wed, Oct 26 2005 5:10 PM
ProblemI’ve been stumped by this problem, of truly separating UI presentation logic and UI specific code....
Tewfik Zeghmi wrote A better design for UI code
on Thu, Oct 27 2005 12:33 AM
ProblemI’ve been stumped by this problem, of truly separating UI presentation logic and UI specific code....
Dan Bunea wrote Model View Presenter - is testing the presenter enough?
on Mon, Nov 28 2005 3:05 AM


Source code: Download


Lately, I have noticed that the Humble Dialog Box or Model...
Dan Bunea wrote Model View Presenter - is testing the presenter enough?
on Mon, Nov 28 2005 8:29 AM


Source code: Download


Lately, I have noticed that the Humble Dialog Box or Model...
Jeremy D. Miller -- The Shade Tree Developer wrote Mock Objects and Stubs: The Bottle Brush of TDD
on Mon, Dec 19 2005 7:38 AM
I’m in a dry spell for blogging, so here’s a rehash of a presentation I gave internally at work earlier...
Jeremy D. Miller -- The Shade Tree Developer wrote Mock Objects and Stubs: The Bottle Brush of TDD
on Mon, Dec 19 2005 10:49 AM
Author: <a href="blogs/jeremy.miller/">Jeremy D. Miller</a><br>You’ll frequently hear teams say they didn’t write unit tests for a particular area of the code because it was just too hard to test. I think one of the biggest challenges of using TDD is learning strategies for isolating the code that’s hard to test and writing code that is easy to test. One of the primary strategies to extend unit test coverage into those hard to reach places is to use mock objects, stubs, or other fake objects in place of the external resources so that your tests don’t involve the external resources at all.
Jeremy D. Miller -- The Shade Tree Developer wrote Mock Objects and Stubs: The Bottle Brush of TDD
on Tue, Dec 20 2005 5:24 PM
Author: <a href="blogs/jeremy.miller/">Jeremy D. Miller</a><br>You’ll frequently hear teams say they didn’t write unit tests for a particular area of the code because it was just too hard to test. I think one of the biggest challenges of using TDD is learning strategies for isolating the code that’s hard to test and writing code that is easy to test. One of the primary strategies to extend unit test coverage into those hard to reach places is to use mock objects, stubs, or other fake objects i
Jeremy D. Miller -- The Shade Tree Developer wrote Mock Objects and Stubs: The Bottle Brush of TDD
on Sat, Jan 14 2006 10:24 AM
Author: <a href="blogs/jeremy.miller/">Jeremy D. Miller</a><br>You’ll frequently hear teams say they didn’t write unit tests for a particular area of the code because it was just too hard to test. I think one of the biggest challenges of using TDD is learning strategies for isolating the code that’s hard to test and writing code that is easy to test. One of the primary strategies to extend unit test coverage into those hard to reach places is to use mock objects, stubs, or other fake objects i
Jeremy D. Miller -- The Shade Tree Developer wrote Test Driven Development with ASP.Net and the Model View Presenter Pattern
on Wed, Feb 1 2006 11:17 PM
A friend of mine was asking me a while back about ways to apply the Model View Presenter (the “Humble...
Jeremy D. Miller -- The Shade Tree Developer wrote Test Driven Development with ASP.Net and the Model View Presenter Pattern
on Thu, Feb 2 2006 7:11 PM
Author: <a href="blogs/jeremy.miller/">Jeremy D. Miller</a><br />A friend of mine was asking me a while back about ways to apply the Model View Presenter (the “Humble Dialog Box”) pattern to ASP.Net development to promote easier unit testing of the user interface layer. Two weeks and a major case of writer’s block later, I finally finished the post. I wrote a blog post last spring describing the usage of MVP (“The Humble Dialog Box”) with WinForms clients, but web development in general and ASP.Net in specific comes with a different set of challenges.
Jeremy D. Miller -- The Shade Tree Developer wrote Things we talked about at the Austin Code Camp
on Mon, Mar 6 2006 8:32 AM
This past Saturday was the Austin Code Camp.  I had a great time and the general consensus was that...
Billy McCafferty wrote The solution for proper ASP.NET MVC
on Wed, Mar 15 2006 7:58 PM
Billy McCafferty wrote The solution for proper ASP.NET MVC
on Wed, Mar 15 2006 10:10 PM
Jeremy D. Miller -- The Shade Tree Developer wrote Stuff coming up at the Shade Tree Developer
on Thu, Jun 29 2006 7:02 PM
If you're in Austin, I'm doing a presentation on StructureMap at the ADNUG meeting on July 10th.
Mea...
Jeremy D. Miller -- The Shade Tree Developer wrote Slides from the StructureMap Talk Last Night at ADNUG
on Tue, Jul 11 2006 10:52 AM
The slide deck and sample code from my StructureMap talk last night is available from the ADNUG downloads...
风满袖 wrote MVP——Model-Viewer-Presenter
on Wed, Jul 12 2006 11:52 PM
这里的MVP不是微软的那个MVP,而是一个设计模式Model-Viewer-Presenter。最早(2000年)由IBM开发出来的一个针对C  和Java的编程模型,它是MVC模式的变种。但我们可以把MVP应用到ASP.NET的应用中,以克服code-behind的各种弊端。
Vivek wrote re: A Simple Example of the "Humble Dialog Box"
on Thu, Aug 30 2007 2:30 AM

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

Rickard Nilsson wrote re: A Simple Example of the "Humble Dialog Box"
on Tue, Jul 15 2008 4:39 PM

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

Add a Comment

(required)  
(optional)
(required)  
Remember Me?