Avoiding Swing Threading Problems

Problem

Solution

Discussion

Suppose you want to test the focus traversal order of the
PersonEditorPanel introduced in the previous
recipe. You want to ensure that focus travels from component to
component in the correct order as the user hits the tab key. To do
this, you write the following test:

public void testTabOrder( ) {
JTextField firstNameField = this.tannerPanel.getFirstNameField( );
firstNameField.requestFocusInWindow( );
// simulate the user hitting tab
firstNameField.transferFocus( );
// ensure that the last name field now has focus
JTextField lastNameField = this.tannerPanel.getLastNameField( );
assertTrue("Expected last name field to have focus",
lastNameField.hasFocus( ));
}

As written, this test fails. First and foremost, the components must
be visible on screen before they can obtain focus. So you try
modifying your setUp( ) method as follows:

This takes advantage of the JFrame provided by our
base class, SwingTestCase. When you run your test
again, it still fails! The initial focus never made it to the first
name field. Here is a partial solution:

public void testTabOrder( ) {
JTextField firstNameField = this.tannerPanel.getFirstNameField( );
// make sure the first name field has focuswhile (!firstNameField.hasFocus( )) {getTestFrame().toFront( );firstNameField.requestFocusInWindow( );}
// simulate the user hitting tab
firstNameField.transferFocus( );
// ensure that the last name field now has focus
JTextField lastNameField = this.tannerPanel.getLastNameField( );
assertTrue("Expected last name field to have focus",
lastNameField.hasFocus( ));
}

This approach keeps trying until the first name field eventually
gains focus. It also brings the test frame to the front of other
windows because, during testing, we found that the frame sometimes
gets buried if the user clicks on any other window while the test is
running. We discovered this by repeating our tests and clicking on
other applications while the tests ran:

We still have one more problem. When the test runs repeatedly, you
will notice that the test fails intermittently. This is because the
transferFocus( ) method does not occur
immediately. Instead, the request to transfer focus is scheduled on
the AWT event queue. In order to pass consistently, the test must
wait until the event has a chance to be processed by the queue. Example 4-15 lists the final version of our test.

Example 4-15. Final tab order test

public void testTabOrder( ) {
JTextField firstNameField = this.tannerPanel.getFirstNameField( );
// make sure the first name field has focus
while (!firstNameField.hasFocus( )) {
getTestFrame().toFront( );
firstNameField.requestFocusInWindow( );
}
// simulate the user hitting tab
firstNameField.transferFocus( );
// wait until the transferFocus( ) method is processedwaitForSwing( );
// ensure that the last name field now has focus
JTextField lastNameField = this.tannerPanel.getLastNameField( );
assertTrue("Expected last name field to have focus",
lastNameField.hasFocus( ));
}

The waitForSwing( ) method is a new feature of our
base class, SwingTestCase, that blocks until
pending AWT events like transferFocus( ) are
processed:

Now, our test runs almost 100% of the time. When repeating the test
thousands of times, you can make the test fail every once in a while
by randomly clicking on other applications. This is because you take
away focus just before the test asks the last name field if it has
focus. This sort of problem is unavoidable, and illustrates one of
the reasons why you should minimize your
dependency on Swing tests.

The most valuable lesson of this recipe is the technique of repeating
your graphical tests many thousands of times until all of the quirky
Swing threading issues are resolved. Once your tests run as
consistently as possible, remove the repeated test. Also, while your
tests are running, avoid clicking on other running applications so
you don’t interfere with focus events.