Designing swing controllers to make them testable

Hello everyone,

I’ve been looking for ages for a design which would allow me to keep my habits even when I’m dealing with a fat UI layer : how should I design my classes so that I can get the max test coverage out of this design ?

If you’re familiar with swing you’ll notice that one often ends up designing behaviours in anonymous classes.
I bet you’re familiar with this kind of code :

...
JButton cancelButton = new JButton(messageSource.getMessage());
cancelButton.addActionListener(new ActionListener(){

			/**
			 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
			 */
			@Override
			public void actionPerformed(ActionEvent e) {
				doSomethingIWouldLikeToTest();
			}

		});
);
...

The problem with such piece of code is that when your application starts to handle many events your code starts to become monolithic and verbose.

My idea was to centralize event handling in one non-anonymous class (one class per view/screen), not as many class as events. This soon reminded me of a controller responsibilities : gathering user input to handle them correctly. One class to rule them all …

Rules started to emerge and I started to notice recurrences in my controllers :
– I never instantiate views directly. Its controller is the only way to instantiate it.
– If needed, the controller provides the different ways to create and display the view (read-only with no getter, at constructor, factory method to get brand new one, singleton or not, etc)
– The relation between a view and a controller is a one-to-one : one controller responsible for one view. In the case of composite views, the controllers are also in relation via composition.
– The view has no knowledge of the controller or the model
– The model has no knowledge of the the controller or the view
– Views are what they are : a composition of atomic UI components designed in a fashion and ergonomic way. No data in the view.
– The controller … controls. Means that it listens to all events that occur on the view (ChangeListener, ActionListener, KeyListener, etc.). It is the only piece of code allowed to register as a listener for its view components.
– Whenever the model is set (reading results from server-side) the view is updated
– When an event occurs on the User Interface, the model is updated and consequently, the view.
– To avoid cyclic event handling remove listeners.

To make you understand my purpose I imagined a basic financial application (based on true work) and provided it with simple uses cases :
– the system should provide the ability for a user to open a deal search screen
– the system should provide the ability for a user to input the following criteria : start date, end date, entity, currency, product type
– the system should provide the ability for a user to choose entities between a defined entities perimeter
– the system should provide the ability for a user to choose product type between a defined product types perimeter
– the system should provide the ability for a user to choose currencies between a defined currencies perimeter : the currencies are bound to the entity. The entity holds a currency set which represent the currencies on which one entity can deal with.
– the system should provide the ability for a user to reset inputs
– the system should provide the ability for a user to start a search and update results consequently

The visual result looks like this :


Some test/code/refactor later I ended designing a controller with those methods :

createView : create an empty view, no referential data, no listeners

	protected void createView() {
		setView(new FindDealsByCriteriaInternalFrame());
		getView().getResultsTable().setModel(
				new FindDealByCriteriaResultsTableModel());
		String[] columns = { "Date", "Entity", "Product type", "Amount",
				"Currency" };
		((FindDealByCriteriaResultsTableModel) getView().getResultsTable()
				.getModel()).setColumns(columns);
	}

loadReferentialData : fills view with referential data


		List entities = referentialService.loadAllEntities();

		if (getModel() == null) {
			model = new FindDealsByCriteriaModel();
		}

		getModel().setEntities(entities);

		if (CollectionUtils.isEmpty(getModel().getEntities())) {
			getView().getEntityComboBox().removeAllItems();
		} else {
			getView().getEntityComboBox().addItem(Entity.EMPTY);
			for (Entity entity : getModel().getEntities())
				if (entity != null) {
					getView().getEntityComboBox().addItem(entity);
				}
		}

		List productTypes = referentialService
				.loadAllProductTypes();

		getModel().setProductTypes(productTypes);

		if (CollectionUtils.isEmpty(getModel().getProductTypes())) {
			getView().getProductTypeComboBox().removeAllItems();
		} else {
			getView().getProductTypeComboBox().addItem(ProductType.EMPTY);
			for (ProductType productType : getModel().getProductTypes()) {
				getView().getProductTypeComboBox().addItem(productType);
			}
		}

addListeners : allows the controller to get notified on view events

	protected void addListeners() {
		getView().getOkButton().addActionListener(this);
		getView().getClearButton().addActionListener(this);
		getView().getEntityComboBox().addActionListener(this);
	}

removeListeners : remove events notifications

	protected void removeListeners() {
		getView().getOkButton().removeActionListener(this);
		getView().getClearButton().removeActionListener(this);
		getView().getEntityComboBox().removeActionListener(this);
	}

updateModelFromView : update the model based on user inputs. Often used before sending data to server-side. This method has been preferred to updating the model as each component focus loss. It sure is less “event oriented” but performs faster as th UI is not constantly processing changes. This design also avoids “events blocking” … Code omitted for brievity reasons

	protected void updateModelFromView() {
                removeListeners();
                ... // Omitted for brievety
                addListeners();
	}

updateViewFromModel : update the view when the model has been reloaded from server-side

onClearButtonClick : clears the model and consequently the view
setModel : sets the model and invokes updateViewFromModel to ensure view is updated whenever model is changed.

onEntitySelectionChange : update currencies list
onOkButtonClick : searches with user inputs and updates results

I could not extract a generic Interface or Abstract method as behaviours vary really much depending on the view component we work on.
Except for the 3 events (onOkButtonClick, onClearButtonClick, onEntityselectionChange) I think this design is generic for this kind of screen.
Without knowing it I was using a design on which some had already work on. I found out that this MVC design has a name : Passive View (Martin Fowler). Fowler says it’s the most testable way to design MVC.

I did not have time to use joda time, I’ll improve next time.
I was really pleased to use infinitest. I must admit I had an “a priori” on that tool. I was affraid it would be to intrusive. But the experience was really pleasant and the immediate feedback is very interesting.
Adopted !

You can download the source codes on GitHub at this repo

To run the example launch the server :

cd ui-unittesting-poc-root/ui-unittesting-poc-server
mvn jetty:run-exploded

Then run the UI

cd ui-unittesting-poc-root/ui-unittesting-poc-ui
mvn exec:java -Dexec.mainClass="org.diveintojee.poc.ui.unittesting.ApplicationLauncher"

Hope you enjoyed the article or at least you learned something.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s