This post originated from an RSS feed registered with .NET Buzz
by Udi Dahan.
Original Post: Testable, Rich UIs
Feed Title: Udi Dahan - The Software Simplist
Feed URL: http://feeds.feedburner.com/UdiDahan-TheSoftwareSimplist
Feed Description: I am a software simplist. I make this beast of architecting, analysing, designing, developing, testing, managing, deploying software systems simple.
This blog is about how I do it.
I've been hearing more and more accounts of decreased ROI of TDD in rich UIs. After analyzing many of them I have come to the conclusion that the problem is not TDD, but rather one of design.
Let's take, for example, a use case where the user can select a customer from a list (or grid) and open the detailed view by double-clicking, or selecting a row and clicking the 'open' button in the toolbar, or the 'File-Open' menu. How would you unit test the code that does this? It does appear to be a unit test, at first glance. After that it begins to look like an integration test. The truth is that it depends on your design.
Here's how I solve this scenario, but I've never heard anyone else do anything remotely like this, so far.
First, I have a command called 'Open' in the GeneralPurposeCommands group. Double clicking the grid, toolbar, menu - all activate that command.
Second, I have a SelectionManager. On this first of the click of a row in the grid, I have the view call the 'Select' method of the SelectionManager. Later, my controller can access the 'SelectedObjects' property of the SelectionManager.
Third, my CustomerController (after having subscribed to the 'Activated' event) handles the Activated event of the 'Open' command by checking to see if the SelectionManager's SelectedObjects property contains a customer. If so, it needs to open the detail view.
Since I don't want the CustomerController to be dependent upon a concrete view, I have an ICustomerDetailView interface which the controller needs to create a new instance of. To do this, it uses an IObjectBuilder and calls the 'T Build()' method with the ICustomerDetailView type parameter.
The IObjectBuilder implementation that was injected into the controller accesses the dependency injection framework to get an instance that matches that interface (which is expected to be single-call, not singleton).
Finally, the controller takes the customer object from the SelectionManager, clones it, passes it to the view, and calls 'Show' on the view. The view takes the given customer object and binds it to all the controls.
Now, think how easy it would be for you to test this functionality. It really decreases in scope back down to unit test level because all we're testing is the controller. By passing in mocks for the selection manager and the object builder, we get a high-quality, expressive scenario that could even be independent of the implementation of the controller.
Let me know what you think of this design. Does it decrease the pain of unit testing in the UI? Do you have a better way?