Developing Custom Swing Components With Test Driven Development

Developing Custom Swing Components With Test Driven Development

By Santosh Shanbhag, OCI Senior Software Engineer

January 2005


Introduction

Developing Swing components while staying on the path of test driven development has no doubt challenged many seasoned Swing developers. Many aspects of GUI testing are indeed not that complicated as long as one adopts the simplicity principle. Moreover, there are quite a few Swing testing frameworks that make it easier to test Swing applications - Abbot and Jemmy being amongst the most popular. This article focuses on developing a couple of custom Swing GUI components using test driven techniques. We will also develop a simple GUI testing helper class that will aid us in testing our custom components. This article does not cover any GUI testing framework and the reader is advised to look them up elsewhere.

Custom Swing Component #1 - A Searchable List Box

A searchable list box provides capability to search a list box for a particular item. The user enters some text in the provided search box. For each keystroke, the list box automatically scrolls down and selects the closest match it has found so far. If a match is not found, the list box does not select anything. The searchable list box is shown in Fig 1.

Figure 1. Searchable List Box

Figure 1. Searchable List Box


Figure 2. Searchable List Box With "United S" typed

Figure 2. Searchable List Box With

Test Driven Development

Keeping a simple approach to development, the initial coding provides a simple SearchableListBox that extends JPanel and is composed of three sub-components: a JLabel that displays a custom text like "Search" or "Find", a JTextField of default length 30 that allows the user to type in the search text, and a list box that contains the list of items that could be searched. There is no functionality at this point other than the basic layout of the components. A simple unit test using the JUnit framework ( http://www.junit.org ) tests the basic display of the panel. There are no asserts yet - just a visual check. The display is shown in Figure 1 above.

JUnit Test: SearchableListBox_UT.java

  1. /*
  2.  * Author: Santosh Shanbhag
  3.  */
  4. package com.ociweb.testinggui;
  5.  
  6. import junit.framework.TestCase;
  7.  
  8. import javax.swing.*;
  9.  
  10. public class SearchableListBox_UT extends TestCase {
  11.  
  12. // String based test data that is used to populate the list items
  13. public static final String[] LIST_DATA = new String[]{
  14. "United Kingdom", "Italy", "India", "Uganda", "Austraila",
  15. "United States", "Austria", "Indonesia"};
  16.  
  17. public SearchableListBox_UT(String name) {
  18. super(name);
  19. }
  20.  
  21. /**
  22.   * Initial Test to check display of components.
  23.   * This test is useful only initially when we are designing the component. It helps us to verify
  24.   * visually that the screen layout of the component is proper. In an automated test environment, it is
  25.   * advisable to comment out this test before commiting the code
  26.   *
  27.   */
  28. public void testDisplay() throws Exception {
  29. JFrame frame = new JFrame();
  30. SearchableListBox searchableListBox = new SearchableListBox(LIST_DATA);
  31. frame.getContentPane().add(searchableListBox);
  32. frame.pack();
  33. frame.setLocationRelativeTo(null);
  34. frame.setVisible(true);
  35. // sleep for 60 seconds so that you can check the display
  36. Thread.sleep(60000);
  37.  
  38. }
  39. }

Code: SearchableListBox.java

  1. /*
  2.  * Author: Santosh Shanbhag
  3.  */
  4. package com.ociweb.testinggui;
  5.  
  6. import javax.swing.*;
  7. import java.awt.*;
  8.  
  9. public class SearchableListBox extends JPanel {
  10.  
  11. // default search label text
  12. public static final String SEARCH_LABEL_DEFAULT_TEXT = "Search:";
  13.  
  14. // default search field length
  15. public static final int SEARCH_FIELD_DEFAULT_LENGTH = 30;
  16.  
  17. private JLabel searchLabel;
  18. private JTextField searchField;
  19. private JList list;
  20.  
  21.  
  22. /**
  23.   * Constructor.
  24.   * @param listData The list box items
  25.   */
  26. public SearchableListBox(Object[] listData) {
  27. init(this.searchLabelTxt, this.searchFieldLength, listData);
  28. }
  29.  
  30. /**
  31.   * Constructor.
  32.   * @param searchLabelTxt
  33.   * The text used to display above the search field. For example: "Search", "Find", "Get it"
  34.   * @param searchFieldLength
  35.   * The length of the search field
  36.   * @param listData
  37.   * The list box items
  38.   */
  39. public SearchableListBox(String searchLabelTxt, int searchFieldLength, Object[] listData) {
  40. init(searchLabelTxt, searchFieldLength, listData);
  41. }
  42.  
  43.  
  44. /**
  45.   * Initialization routine to create GUI components.
  46.   * @param searchLabelTxt
  47.   * The text used to display above the search field. For example: "Search", "Find", "Get it"
  48.   * @param searchFieldLength
  49.   * The length of the search field
  50.   * @param listData
  51.   * The list box items
  52.   */
  53. private void init(String searchLabelTxt, int searchFieldLength, Object[] listData) {
  54. searchLabel = new JLabel(searchLabelTxt);
  55. searchField = new JTextField(searchFieldLength);
  56. list = new JList(listData);
  57.  
  58. // we would like to see the first ten items
  59. list.setVisibleRowCount(10);
  60.  
  61. // limit the selection to a single item
  62. list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
  63. layoutComponents();
  64. }
  65.  
  66. /**
  67.   * Lays out GUI components on the screen.
  68.   */
  69. private void layoutComponents() {
  70. setLayout(new BorderLayout());
  71.  
  72. // create a search panel to contain search label and
  73. // search field.
  74. JPanel searchPanel = new JPanel(new BorderLayout());
  75. searchPanel.add(searchLabel, BorderLayout.NORTH);
  76. searchPanel.add(searchField, BorderLayout.SOUTH);
  77.  
  78. // put list box in the scroll pane
  79. JScrollPane scrollpaneForList = new JScrollPane(list,
  80. JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
  81. JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
  82.  
  83. add(searchPanel, BorderLayout.NORTH);
  84. add(scrollpaneForList, BorderLayout.CENTER);
  85. }
  86.  
  87. }

Testing the construction non-visually

At this point we are probably satisfied with the basic display and are ready to implement the search functionality. However, we still need an additional test that will verify if anything breaks in the basic construction of the component. So we add another test to the UT class as shown below. We also have a GUITestHelper class that has static methods to help us with the testing. The code for the GUITestHelper is provided at the end of this article. We still have no way of finding out if the components are laid out correctly but rely instead on the visual test.

  1. public void testConstruction() throws Exception {
  2.  
  3. // for testing, use "Find" text instead of the default "Search"
  4. String searchLabelTxt = "Find";
  5. int searchFieldLength = 20;
  6.  
  7. SearchableListBox searchableListBox = new SearchableListBox(searchLabelTxt, searchFieldLength, LIST_DATA);
  8.  
  9. // find out if a JLabel with "Find" text is contained in the custom component
  10. JLabel searchLabel = GUITestHelper.findJLabelContainingText(searchableListBox, searchLabelTxt);
  11. assertNotNull("Could not find a JLabel with text: " + searchLabelTxt, searchLabel);
  12.  
  13. // find out if a JTextField of length 20 is contained in the custom component
  14. JTextField textField = GUITestHelper.findJTextFieldOfLength(searchableListBox, searchFieldLength);
  15. assertNotNull("Could not find a JTextField of length: " + searchFieldLength, textField);
  16.  
  17. // test that the list contains same number of items that were passed in
  18. assertEquals(LIST_DATA.length, searchableListBox.getItemCount());
  19.  
  20. // As of now, test that the list maintains the order of the items that were passed in.
  21. // But in the future, if someone sorts the order of the items in the list, this test
  22. // will need to be updated. At that time, the updated test will ensure that the code
  23. // that keeps the list items in a sorted order is not broken.
  24. for (int i = 0; i < LIST_DATA.length; i++) {
  25. String expectedListItem = LIST_DATA[i];
  26. assertEquals("List Item not in the order it was passed in",
  27. expectedListItem, searchableListBox.getItemAt(i));
  28. }
  29.  
  30. }

Implementing the search functionality

The next step is to test and implement the search functionality. The JTextField stores it's data in a model whose class is javax.swing.text.Document. A document listener object (javax.swing.event.DocumentListener) can be associated with the Document that intercepts insertions, deletions, and changes made to it. We define our DocumentListener class as shown below:

  1. /*
  2.  * Custom document listener class that is used by SearchableListBox to intercept text field changes.
  3.  * Just to make sure all implemented methods call doUpdate().
  4.  *
  5.  * Author: Santosh Shanbhag
  6.  */
  7. package com.ociweb.testinggui;
  8.  
  9. import javax.swing.event.DocumentListener;
  10.  
  11. public abstract class CustomDocumentListener implements DocumentListener {
  12.  
  13. public void changedUpdate(DocumentEvent e) {
  14. doUpdate();
  15. }
  16.  
  17. public void insertUpdate(DocumentEvent e) {
  18. doUpdate();
  19. }
  20.  
  21. public void removeUpdate(DocumentEvent e) {
  22. doUpdate();
  23. }
  24.  
  25. public abstract void doUpdate();
  26.  
  27. }

Now all we have to do is plug this document listener to the JTextField's Document model and implement the doUpdate() method. The doUpdate() method will implement the search functionality. We create an inner class in SearchableListBox that extends CustomDocumentListener so that the listener has access to both the search text field and the list box. This is shown in the code below:

  1. private void init(String searchLabelTxt, int searchFieldLength, Object[] listData) {
  2. this.listData = listData;
  3. list = new JList(listData);
  4.  
  5. searchLabel = new JLabel(searchLabelTxt);
  6. searchField = new JTextField(searchFieldLength);
  7.  
  8. // we would like to see the first ten items
  9. list.setVisibleRowCount(10);
  10.  
  11. // limit the selection to a single item
  12. list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
  13.  
  14. // add a document listener to the text field
  15. SearchFieldDocumentListener searchFieldListener = new SearchFieldDocumentListener();
  16. searchField.getDocument().addDocumentListener(searchFieldListener);
  17.  
  18.  
  19. // lay out GUI components
  20. layoutComponents();
  21. }
  22.  
  23. // returns the currently selected item in the list box
  24. public Object getSelectedItem() {
  25. return list.getSelectedValue();
  26. }
  27.  
  28. //******* inner class of SearchableListBox
  29. class SearchFieldDocumentListener extends CustomDocumentListener {
  30.  
  31. public void doUpdate() {
  32. doSearch();
  33. }
  34.  
  35. }

The SearchFieldDocumentListener implements the doUpdate() method by making a call to the doSearch() method in the SearchableListBox class. This keeps the listener code from referring any objects of SearchableListBox directly thus reducing dependency between the two classes. Next we have to write a test to make sure that doSearch() implementation is correct. This is shown below:

  1. public void testSearchImplementation() throws Exception {
  2.  
  3. String[] LIST_ITEMS = new String[]{
  4. "United Kingdom", "Italy", "India", "Uganda", "Australia", "Chile", "Brazil", "United States", "Austria",
  5. "Indonesia", "Indiana", "Oklahoma", "New Jersey", "New Hampshire", "Africa"};
  6.  
  7. SearchableListBox searchableListBox = new SearchableListBox(LIST_ITEMS);
  8.  
  9. // get a handle on the search text field
  10. int searchFieldDefaultLength = SearchableListBox.SEARCH_FIELD_DEFAULT_LENGTH;
  11. JTextField textField = GUITestHelper.findJTextFieldOfLength(searchableListBox, searchFieldDefaultLength);
  12. assertNotNull("Could not find a JTextField of length: " + searchFieldDefaultLength, textField);
  13.  
  14.  
  15. textField.setText("Austri");
  16. searchableListBox.getSelectedItem();
  17. assertEquals("Austria", searchableListBox.getSelectedItem());
  18.  
  19. textField.setText("Austra");
  20. searchableListBox.getSelectedItem();
  21. assertEquals("Australia", searchableListBox.getSelectedItem());
  22.  
  23. textField.setText("Af");
  24. searchableListBox.getSelectedItem();
  25. assertEquals("Africa", searchableListBox.getSelectedItem());
  26.  
  27. textField.setText("United");
  28. searchableListBox.getSelectedItem();
  29. assertEquals("United Kingdom", searchableListBox.getSelectedItem());
  30.  
  31. textField.setText("United K");
  32. searchableListBox.getSelectedItem();
  33. assertEquals("United Kingdom", searchableListBox.getSelectedItem());
  34.  
  35. textField.setText("United S");
  36. searchableListBox.getSelectedItem();
  37. assertEquals("United States", searchableListBox.getSelectedItem());
  38.  
  39. textField.setText("New");
  40. searchableListBox.getSelectedItem();
  41. assertEquals("New Jersey", searchableListBox.getSelectedItem());
  42.  
  43. textField.setText("New H");
  44. searchableListBox.getSelectedItem();
  45. assertEquals("New Hampshire", searchableListBox.getSelectedItem());
  46.  
  47. textField.setText("Ug");
  48. searchableListBox.getSelectedItem();
  49. assertEquals("Uganda", searchableListBox.getSelectedItem());
  50. }

Finally the implementation of the doSearch() method is shown below:

  1. private void doSearch() {
  2.  
  3. // find the closest matching item
  4. int searchItemIndex = findSearchItemIndex(list, searchField.getText());
  5.  
  6. // clear any previous selections
  7. list.clearSelection();
  8.  
  9. list.setSelectedIndex(searchItemIndex);
  10.  
  11. // scroll down to make sure the selected item is visible
  12. list.ensureIndexIsVisible(Math.max(searchItemIndex, 0));
  13. }
  14.  
  15. private int findSearchItemIndex(JList inputList, String searchString) {
  16.  
  17. // if there is nothing in the search text field just return -1
  18. if ("".equals(searchString)) return -1;
  19.  
  20. int indexOfSearchedItem = -1;
  21. for (int i = 0; i < listData.length; i++) {
  22. String listItemString = listData[i].toString();
  23.  
  24. // if the search string is contained at the start of the list item string, we found it!!!
  25. if (listItemString.indexOf(searchString) == 0) {
  26. indexOfSearchedItem = i;
  27. break;
  28. }
  29. }
  30.  
  31. // make sure we never go out of array bounds
  32. return Math.min(indexOfSearchedItem, listData.length - 1);
  33. }

Custom Swing Component #2 - Two List Boxes With Inter-Transferable Items

Here we design and test a component that has two list boxes and two buttons. When the first button is clicked, the selected item from the first list box is transferred to the second. When the second button is clicked, the selected item from the second list box is transferred to the first. These are the basic requirements of this component. The component is shown in figure 3 below.

Figure 3. Two List Boxes With Inter-Transferable Items

Figure 3. Two list Boxes With Inter-Transferable Items

Test Driven Development

Keeping a simple approach to development just like for SearchableListBox before, the intial coding provides a simple component ListBoxesWithTransferableItems that extends JPanel and is composed of six sub-components: two JLabels that display a text for each list box, two J Buttons one for transfer from left-to-right and vice- versa, and two JLists. There is no functionality at this point other than the basic layout of the components. A simple unit test using the JUnit framework tests the basic display of the panel. There are no asserts yet - just a visual check. The display is shown in Figure 3 above.

JUnit Test: ListBoxesWithTransferableItems_UT.java

  1. /*
  2.  * UT class for testing ListBoxesWithTransferableItems.
  3.  *
  4.  * Author: Santosh Shanbhag
  5.  */
  6. package com.ociweb.testinggui;
  7.  
  8. import junit.framework.TestCase;
  9.  
  10. import javax.swing.*;
  11. import java.awt.*;
  12.  
  13. public class ListBoxesWithTransferableItems_UT extends TestCase {
  14.  
  15. public ListBoxesWithTransferableItems_UT(String name) {
  16. super(name);
  17. }
  18.  
  19. /**
  20.   * Initial Test to check display of components.
  21.   * This test is useful only initially when we are designing the component. It helps us to verify
  22.   * visually that the screen layout of the component is proper. In an automated test environment, it is
  23.   * advisable to comment out this test before committing the code
  24.   *
  25.   */
  26. public void testDisplay() throws Exception {
  27. String[] LIST_DATA_LEFT = {"Apples", "Bananas", "Oranges", "Grapes", "WaterMelons", "HoneyDew", "Nectarines"};
  28. String[] LIST_DATA_RIGHT = {"Peaches", "Cantaloupes", "Figs"};
  29. ListBoxesWithTransferableItems listBoxesWithTransferableItems
  30. = new ListBoxesWithTransferableItems(LIST_DATA_LEFT, LIST_DATA_RIGHT);
  31. listBoxesWithTransferableItems.setBorder(BorderFactory.createLineBorder(Color.black));
  32.  
  33. // create a test frame to show the panel
  34. JFrame frame = new JFrame();
  35. frame.getContentPane().add(listBoxesWithTransferableItems);
  36. frame.pack();
  37.  
  38. // center the frame on the screen
  39. frame.setLocationRelativeTo(null);
  40. frame.show();
  41.  
  42. // 1-minute delay to see the component
  43. Thread.sleep(60000);
  44.  
  45. }
  46. }

Code: ListBoxesWithTransferableItems.java

  1. /*
  2.  * GUI Component that has two list boxes with items that can be transferred between them.
  3.  *
  4.  * Author: Santosh Shanbhag
  5.  */
  6. package com.ociweb.testinggui;
  7.  
  8. import javax.swing.*;
  9. import java.awt.*;
  10.  
  11. public class ListBoxesWithTransferableItems extends JPanel {
  12.  
  13. // label that will be displayed above the left list box
  14. private JLabel labelForLeftListBox = new JLabel("Left List Box");
  15.  
  16. // label that will be displayed above the right list box
  17. private JLabel labelForRightListBox = new JLabel("Right List Box");
  18.  
  19. private JList leftListBox;
  20. private JList rightListBox;
  21.  
  22. // on clicking, items will be transferred from left to right list box
  23. private JButton transferFromLeftToRightButton = new JButton(">>");
  24.  
  25. // on clicking, items will be transferred from right to left list box
  26. private JButton transferFromRightToLeftButton = new JButton("<<");
  27.  
  28. /**
  29.   * Constructor.
  30.   *
  31.   * @param listDataForLeftList - data for left side list box
  32.   * @param listDataForRightList - data for right side list box
  33.   */
  34. public ListBoxesWithTransferableItems(Object[] listDataForLeftList, Object[] listDataForRightList) {
  35. init(listDataForLeftList, listDataForRightList);
  36. }
  37.  
  38. /**
  39.   * Constructor.
  40.   *
  41.   * @param listDataForLeftList - data for left side list box
  42.   * @param listDataForRightList - data for right side list box
  43.   * @param leftListBoxLabelTxt - label text for left side list box
  44.   * @param rightListBoxLabelTxt - label text for right side list box
  45.   */
  46. public ListBoxesWithTransferableItems(Object[] listDataForLeftList, Object[] listDataForRightList,
  47. String leftListBoxLabelTxt, String rightListBoxLabelTxt) {
  48. labelForLeftListBox.setText(leftListBoxLabelTxt);
  49. labelForRightListBox.setText(rightListBoxLabelTxt);
  50. init(listDataForLeftList, listDataForRightList);
  51. }
  52.  
  53. private void init(Object[] listDataForLeftList, Object[] listDataForRightList) {
  54. leftListBox = new JList(listDataForLeftList);
  55. rightListBox = new JList(listDataForRightList);
  56. layoutComponents();
  57. }
  58.  
  59. // use a layout manager to lay out sub components in the panel
  60. private void layoutComponents() {
  61. add(createPanelForListBoxWithLabel(leftListBox, labelForLeftListBox));
  62. JPanel buttonPanel = new JPanel(new BorderLayout());
  63. buttonPanel.add(transferFromLeftToRightButton, BorderLayout.NORTH);
  64. buttonPanel.add(transferFromRightToLeftButton, BorderLayout.SOUTH);
  65. add(buttonPanel);
  66. add(createPanelForListBoxWithLabel(rightListBox, labelForRightListBox));
  67. }
  68.  
  69. // create a panel with label displayed above the list box.
  70. // put list box in a scroll pane.
  71. private JPanel createPanelForListBoxWithLabel(JList listBox, JLabel label) {
  72. JPanel panel = new JPanel(new BorderLayout());
  73. panel.add(label, BorderLayout.NORTH);
  74. JScrollPane scrollPaneForListBox = new JScrollPane(listBox);
  75. panel.add(scrollPaneForListBox, BorderLayout.CENTER);
  76. return panel;
  77. }
  78.  
  79. }

The GUI has no functionality yet so we move on to the next step. The JButtons need an ActionListener that will act on the list boxes (transferring an item from one to the other) when clicked. Using an inner class as the listener we provide a method stub in the ListBoxesWithTransferableItems class that will be called every time a button is clicked. This is shown in the code below (ListBoxesWithTransferableItems.java).

  1. private void transferSelectedItemFromRightListBoxToLeft() {
  2. }
  3.  
  4. private void transferSelectedItemFromLeftListBoxToRight() {
  5. }
  6.  
  7. /**
  8.  * ******* Inner Class *************
  9.  */
  10. class ButtonActionListener implements ActionListener {
  11.  
  12. public void actionPerformed(ActionEvent e) {
  13.  
  14. JButton sourceButton = (JButton) e.getSource();
  15.  
  16. if (sourceButton == transferFromLeftToRightButton) {
  17.  
  18. // transfer from left to right
  19. transferSelectedItemFromLeftListBoxToRight();
  20. }
  21. else if (sourceButton == transferFromRightToLeftButton) {
  22.  
  23. // transfer from right to left
  24. transferSelectedItemFromRightListBoxToLeft();
  25. }
  26. }
  27. }

Since we are following test driven development, we should write a unit test at this point to verify that selected items will be transferred from one list box to the other when the appropriate buttons are clicked. Here's the test for transferring from left list to right list By making slight changes, we can easily write another test that verifies the transfer of items from right-to-left. The code is provided in the listing at the bottom.

  1. public void testTransferItemsFromLeftListToRightList() throws Exception {
  2.  
  3. ListBoxesWithTransferableItems listBoxesWithTransferableItems
  4. = new ListBoxesWithTransferableItems(LIST_DATA_LEFT, LIST_DATA_RIGHT);
  5.  
  6. JButton leftToRightTransferButton = GUITestHelper.findButtonWithText(listBoxesWithTransferableItems, ">>");
  7. assertNotNull(leftToRightTransferButton);
  8.  
  9. // assert that left list contains expected data
  10. Object[] leftListData = listBoxesWithTransferableItems.getAllItemsFromLeftListBox();
  11. assertTrue(Arrays.equals(LIST_DATA_LEFT, leftListData));
  12.  
  13. // assert that right list contains expected data
  14. Object[] rightListData = listBoxesWithTransferableItems.getAllItemsFromRightListBox();
  15. assertTrue(Arrays.equals(LIST_DATA_RIGHT, rightListData));
  16.  
  17. // click the left-to-right transfer button
  18. leftToRightTransferButton.doClick();
  19.  
  20. // there was nothing selected in left list, so nothing should be removed from left list
  21. leftListData = listBoxesWithTransferableItems.getAllItemsFromLeftListBox();
  22. assertTrue(Arrays.equals(LIST_DATA_LEFT, leftListData));
  23.  
  24. // there was nothing selected in left list, so nothing should be added to right list
  25. rightListData = listBoxesWithTransferableItems.getAllItemsFromRightListBox();
  26. assertTrue(Arrays.equals(LIST_DATA_RIGHT, rightListData));
  27.  
  28. listBoxesWithTransferableItems.selectItemInLeftListAtIndex(0);
  29. leftToRightTransferButton.doClick();
  30.  
  31. assertEquals("Items in left list should have decreased by 1",
  32. LIST_DATA_LEFT.length - 1, listBoxesWithTransferableItems.getAllItemsFromLeftListBox().length);
  33. assertEquals("Items in left list should have increased by 1",
  34. LIST_DATA_RIGHT.length + 1, listBoxesWithTransferableItems.getAllItemsFromRightListBox().length);
  35.  
  36. }

and here's the source code for the transfer from left-to-right!!!

  1. private void transferSelectedItemFromLeftListBoxToRight() {
  2. int selectedIndex = getSelectedIndexOfLeftListBox();
  3.  
  4. // return if no selected item
  5. if (selectedIndex == -1) return;
  6.  
  7. Object selectedItemFromLeftListBox = getSelectedItemFromLeftListBox();
  8.  
  9. // remove selected item from left list
  10. DefaultListModel leftListModel = (DefaultListModel) leftListBox.getModel();
  11. leftListModel.removeElement(selectedItemFromLeftListBox);
  12.  
  13. // add removed item from left list to right list
  14. DefaultListModel rightListModel = (DefaultListModel) rightListBox.getModel();
  15. rightListModel.addElement(selectedItemFromLeftListBox);
  16. }

Miscellaneous - GUITestHelper Class

The GUITestHelper provides utility methods to find Swing components contained in a container. The components can be searched for using some conditions such as their text contents (JLabel) or field length (JTextField). One could expand this utility to find other GUI components such as JButtonsJMenuItems, etc. The code for GUITestHelper is shown below.

  1. /*
  2.  * Class with static helper methods for GUI testing.
  3.  *
  4.  * Author: Santosh Shanbhag
  5.  */
  6. package com.ociweb.testinggui;
  7.  
  8. import javax.swing.*;
  9. import java.awt.*;
  10.  
  11. public class GUITestHelper {
  12.  
  13. /**
  14.   * Find out if the container and its sub-containers contain a JLabel with the specified
  15.   * text.
  16.   * @param container
  17.   * @param labelText
  18.   * @return JLabel with the specified text, if found. Otherwise, return null.
  19.   */
  20. public static JLabel findJLabelContainingText(Container container, String labelText) {
  21. Component[] components = container.getComponents();
  22. JLabel labelToFind = null;
  23.  
  24. for (int i = 0; i < components.length; i++) {
  25. Component component = components[i];
  26.  
  27. // if the component is a container, find out if it contains the JLabel
  28. if (component instanceof Container) {
  29. // recursion!!!
  30. labelToFind = findJLabelContainingText((Container) component, labelText);
  31. if (labelToFind != null) break;
  32. }
  33.  
  34. // if the component is a JLabel, find out if it contains the text
  35. if (component instanceof JLabel) {
  36. JLabel label = (JLabel) component;
  37. // compare the label text with the text passed in
  38. if (label.getText().equals(labelText)) {
  39. labelToFind = label;
  40. break;
  41. }
  42. }
  43. }
  44. return labelToFind;
  45. }
  46.  
  47. /**
  48.   * Find out if the container and its sub-containers contain a JTextField of the specified length
  49.   * @param container
  50.   * @param fieldLength
  51.   * @return JTextField having the specified length, if found. Otherwise, return null.
  52.   */
  53. public static JTextField findJTextFieldOfLength(Container container, int fieldLength) {
  54. Component[] components = container.getComponents();
  55. JTextField textFieldToFind = null;
  56. for (int i = 0; i < components.length; i++) {
  57. Component component = components[i];
  58.  
  59. // if the component is a container, find out if it contains the JTextField
  60. if (component instanceof Container) {
  61.  
  62. // recursion!!!
  63. textFieldToFind = findJTextFieldOfLength((Container) component, fieldLength);
  64. if (textFieldToFind != null) break;
  65. }
  66.  
  67. // if the component is a JTextField, find out if it is of the specified length
  68. if (component instanceof JTextField) {
  69. JTextField textField = (JTextField) component;
  70.  
  71. // compare the length of text field with that passed in
  72. if (textField.getColumns() == fieldLength) {
  73. textFieldToFind = textField;
  74. break;
  75. }
  76. }
  77. }
  78. return textFieldToFind;
  79. }
  80.  
  81.  
  82. /**
  83.   * Find out if the container and it's sub-containers contain a JButton having the specified text.
  84.   *
  85.   * @param container
  86.   * @param buttonText
  87.   * @return JButton containing the text, if found. Otherwise, return null.
  88.   */
  89. public static JButton findButtonWithText(Container container, String buttonText) {
  90. Component[] components = container.getComponents();
  91.  
  92. JButton buttonToFind = null;
  93. for (int i = 0; i < components.length; i++) {
  94. Component component = components[i];
  95.  
  96. // if the component is a container, find out if it contains the JButton
  97. if (component instanceof Container) {
  98. buttonToFind = findButtonWithText((Container) component, buttonText);
  99. if (buttonToFind != null) break;
  100. }
  101.  
  102. // if the component is a JButton, find out if it contains the text
  103. if (component instanceof JButton) {
  104. JButton button = (JButton) component;
  105. // compare the length of text field with that passed in
  106. if ( button.getText().equals(buttonText) ) {
  107. buttonToFind = button;
  108. break;
  109. }
  110. }
  111. }
  112. return buttonToFind;
  113. }
  114.  
  115. }

Summary

Java Swing provides a framework of sophisticated components. Testing Swing applications is challenging but not overly complicated. Indeed there are many GUI testing frameworks available that ease the process of GUI testing. This article demonstrated using the test driven approach to developing custom GUI components using Swing and JUnit. A GUI test helper class provides useful methods to find components without the need to expose them using accessors in the original class.

References