====== Testing Graphical User Interfaces with JUnit ====== There are some very sophisticated tools that can be used to test Java graphical user interfaces. However, one can accomplish a lot using JUnit and a few "tricks". ===== Mouse and Keyboard (Low-Level) Events ===== Low-level mouse and keyboard events can be generated using a ''java.awt.Robot()'' object. Note that the component must be visible to use a ''Robot''. Hence, it must be added to a top-level container that is, itself, visible. There are some important consequences of this: - The testing must **not** be done in a "headless" environment (i.e., the testing must be conducted on a device that has a display and keyboard) or a virtual display and keyboard must be available (e.g., using 'Xvfb and the ''run-xvfb'' command in Unix). - The top-level container must be disposed of (using ''dispose()'') after the tests have been run (so that the event dispatch thread terminates). Also, remember not to manually move the mouse or use the keyboard while the tests are being run! ===== Button Presses ===== The ''javax.swing.AbstractButton'' class (which is specialized by the ''javax.swing.JButton'', ''javax.swing.JToggleButton'', and ''Javax.swing.JMenuItem'' classes) has a ''doClick()'' method that programmatically generates a button press. ===== Text Events ===== Text components can be tested both directly and indirectly. To test them directly, one can use a ''Robot'' object to move the cursor to the component, click the mouse (i.e., invoke ''mousePress()'' and ''mouseRelease()'' without moving the mouse in-between) to give the component the focus, and then invoke ''keyPress()'' and ''keyRelease()''. One can move the cursor to the component using the information available from its ''getBounds()'' method (which returns relative coordinates) and converting them to screen coordinates using the ''SwingUtilities.convertRectangle()'' method. To test them indirectly, one can use the ''Document'' object (the model in the sense of the model-view-controller pattern) associated with the text component, or the ''setText()'' method in the component itself. ===== Other Components ===== Most other components can also be tested directly (using the techniques described in the section on text events) or indirectly (using the model associated with the component). ===== An Example ===== This section contains an example of using JUnit to test a GUI component that responds to mouse events and button events. ==== The GUI Component ==== import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ClickPanel extends JPanel implements ActionListener, MouseListener { JButton resetButton; private int count; public ClickPanel() { super(); reset(); addMouseListener(this); resetButton = new JButton("Reset"); resetButton.addActionListener(this); setLayout(new BorderLayout()); add(resetButton, BorderLayout.SOUTH); } public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals("Reset")) reset(); } public int getCount() { return count; } public void mouseClicked(MouseEvent e) { count++; } public void reset() { count = 0; } public void mouseEntered(MouseEvent e){ } public void mouseExited(MouseEvent e){ } public void mousePressed(MouseEvent e){ } public void mouseReleased(MouseEvent e){ } } ==== The JUnit Test ==== import static org.junit.jupiter.api.Assertions.*; import java.awt.*; import java.awt.event.InputEvent; import javax.swing.*; import org.junit.jupiter.api.Test; class ClickPanelTest { @Test void test() throws AWTException { ClickPanel panel = new ClickPanel(); // Use a JWindow (rather than a JFrame) because it is unadorned JWindow window = new JWindow(); JPanel contentPane = (JPanel)window.getContentPane(); contentPane.setLayout(new BorderLayout()); contentPane.add(panel, BorderLayout.CENTER); window.setSize(400, 400); window.setLocation(200, 200); window.setVisible(true); // Test mouse handling Robot robot = new Robot(); robot.setAutoDelay(100); robot.setAutoWaitForIdle(true); robot.mouseMove(300, 300); // Absolute screen coordinates robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); assertEquals(0, panel.getCount()); robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); assertEquals(1, panel.getCount()); robot.mouseMove(400, 400); robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); assertEquals(2, panel.getCount()); robot.waitForIdle(); // Test button handling panel.resetButton.doClick(); assertEquals(0, panel.getCount()); window.setVisible(false); window.dispose(); // So the event dispatch thread terminates } }