Making TDD and Java Swing behave

Recently, I wrote about using the JfcUnit and Abbot frameworks to test-drive the creation of a Java Swing GUI. Since then, a post by Liz Keogh on the ExtremeProgramming yahoogroup led me to another option. It a wrapper around Swing written in conjunction with JBehave, but as Liz points out, it’s not dependent on the framework and can happily be used with JUnit, also. I like the fact that it’s really lightweight and fast.

I’ve had a few teething pains getting started. The JBehave Swing wrapper isn’t really documented. That’s not surprising, considering it’s purpose is to allow demonstrating JBehave on a demonstration project. Liz has been very helpful in answering my questions. Maybe this post will help other avoid the mistakes I made.

Your primary point of contact with this code will be the DefaultWindowWrapper class. Create an instance of this with the name of the root window you’ll be testing. Then, create and make the window visible. The only way the WindowWrapper can find the root window is by listening for the create event.

Another thing that I’ve learned in the past, but is not intuitive, is that you must let Swing display itself for everything to work. Some parts of the Swing library finish their initialization when they’re painted to the screen. My setup code for testing a subpanel looks like this:

protected void setUp() throws Exception {
    super.setUp();
    // windowWrapper must be created before window is made visible
    // in order for it to find the window by the creation event.
    windowWrapper = new DefaultWindowWrapper("TestWindow");
    idler = new Idler();
    // setup the object(s) under test
    settings = new Settings();
    uut = new SettingsPanel(settings);
    // run the object under test in a test window
    JFrame windowSoThingsWork = new JFrame();
    windowSoThingsWork.getContentPane().add(uut);
    windowSoThingsWork.pack();
    windowSoThingsWork.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    windowSoThingsWork.setName("TestWindow");
    windowSoThingsWork.setVisible(true);
}

Finding a field by name is trivial

Component component = windowWrapper.findComponent(componentName);

Typing new contents into a text field is similar to JfcUnit, but since there’s no method to triple-click the field, I delete the characters instead.

private void replaceFieldContents(String fieldName, String newValue)
        throws ComponentFinderException, TimeoutException {
    Component field = selectComponent(fieldName);
    deleteFieldContents(field);
    windowWrapper.enterText(fieldName, newValue);
    windowWrapper.pressKeycode(KeyEvent.VK_ENTER);
    idler.waitForIdle();
}

private void deleteFieldContents(Component field) throws TimeoutException {
    if (field instanceof JTextComponent) {
        JTextComponent jtextComponent = (JTextComponent) field;
        while (jtextComponent.getText().length() > 0) {
            windowWrapper.pressKeycode(KeyEvent.VK_DELETE);
        }
    }
}

Notice the idler.waitForIdle() call. This wrapper is so fast, that it can get ahead of the Swing code. This forces it to wait until Swing has processed all its events. I found this especially important when sending KeyEvents to the JComboBox. Without it, the first event after requesting focus sometimes got lost. The keypress was sent before the component had grabbed the focus. I combined the component finder and focuser into one method to make this easy.

private Component selectComponent(String componentName)
        throws ComponentFinderException, TimeoutException {
    Component component = windowWrapper.findComponent(componentName);
    if (!component.hasFocus()) {
        component.requestFocus();
        idler.waitForIdle();
    }
    return component;
}

I ran into issues with an editable JComboBox, and haven’t tracked those down, yet. But if I don’t call comboBox.setEditable(true) then I can select by typing the value using replaceFieldContents() just like for text fields, or using navigation keys:

private void pressKeys(int[] keys) throws TimeoutException {
    for (int i = 0; i < keys.length; i++) {
        windowWrapper.pressKeycode(keys[i]);
    }
    idler.waitForIdle();
}

This is called by

selectComponent("CommPortSelector");
int[] keys = {
    KeyEvent.VK_HOME,
    KeyEvent.VK_DOWN,
    KeyEvent.VK_DOWN,
    KeyEvent.VK_ENTER,
    KeyEvent.VK_TAB
};
pressKeys(keys);

In conclusion, this JBehave extension isn't quite as fully formed as Abbot, but in some ways it surpasses it. Right now, I'm very tempted to use it for production work and see how it goes.

Post to Twitter Post to Plurk Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to MySpace Post to Ping.fm Post to Reddit Post to StumbleUpon

Comments (2) to “Making TDD and Java Swing behave”

  1. I’m curious to know in what ways this surpasses Abbot, since you don’t mention them here.

    Lack of automatic synchronization of generated events with the underlying components’ state, as well as lack of support for setting up and showing UI components on the EDT would seem to be critical omissions.

  2. Timothy,

    I wasn’t able to get Abbot to manipulate the JComboBox via text input, but maybe that’s my ignorance.

    Certainly the Swing wrapper of JBehave is missing a lot of useful stuff, but I wouldn’t call it critical. And it’s not even a project, itself. It doesn’t have a goal of being a framework for GUI TDD. It just happens to do a fairly good job, having been created just to demonstrate JBehave.

    Perhaps you’d like to look at my earlier article where I tried out Abbot. I’d appreciate any feedback on it. I found relatively little documentation on using Abbot for TDD.

Post a Comment
*Required
*Required (Never published)