Testing a Java swing application using Fest

Testing a Java swing application using Fest

18 April 2013

I am tasked with implementing new scenarios and bug fixing an application called the Migration Tool. There are currently Unit tests that are verifying the behaviour of the internal model but now I wanted to test the User Interface.

Creating TestNG class

public class AbstractUITest extends FestSwingTestngTestCase {

    private FrameFixture window;

    @Override
    protected void onSetUp() {
        MigrationTool frame = GuiActionRunner.execute(new GuiQuery<MigrationTool>() {
              protected MigrationTool executeInEDT() {
                  MigrationTool.main(new String[0]);
                  return MigrationTool.getInstance();  
              }
          });
          setWindow(new FrameFixture(robot(),frame.getFrame()));
          getWindow().show();
    }   

    @Override protected void onTearDown() {
        super.onTearDown();     
        getWindow().close();
        MigrationTool.dispose();
    }

    public FrameFixture getWindow() {
        return window;
    }
    private void setWindow(FrameFixture window) {
        this.window = window;
    }
}

MigrationTool is the name of the swing application I was instantiating.
FEST just wraps your application so that you can simulate user interaction.

My first test

My first test began with ensuring that when initially displayed that the tabs were available.

/**
 * Ensures that the following panels exist when started:
 * 
 *  - Export
 *  - Import
 *  - Version Report
 *  - Logging
 */
@Test
public void tabsAreAvailable() {            
    FrameFixture window = getWindow();

    String[] tabTitles = window.tabbedPane().tabTitles();
    Assert.assertEquals(4, tabTitles.length);
    Assert.assertEquals(tabTitles[0], "Export");
    Assert.assertEquals(tabTitles[1], "Import");
    Assert.assertEquals(tabTitles[2], "Logging");
    Assert.assertEquals(tabTitles[3], "Version Report");
}   

You have to start somewhere :)

Fluent API

I am very fond on a Fluent API and I relate it to a PageObject pattern for describing your application services.

So my resulting test is:

@Test
public void companyViewSelectorShouldBeSelected() {
    withMigrationTool()
        .selectExportPanel()
        .viewIsSelected("Companies", new ArgAction<Boolean>() {               
            @Override public void action(Boolean arg) {
                //by default the view should not be selected
                Assert.isTrue(!arg);
            }
        })  
        .selectMigrant("Companies/Companies/Not Specified")
        .viewIsSelected("Companies", new ArgAction<Boolean>() {               
            @Override public void action(Boolean arg) {
                //a migrant select should cause a view to get selected
                Assert.isTrue(arg);
            }
        });     
}

Challenges

I had a few challenges testing my Swing application. They’re listed here.

Clicking a checkbox on com.jidesoft.swing.CheckBoxList

I had trouble clicking a checkbox. In fact it wasn’t a CheckBox but a com.jidesoft.swing.CheckBoxList.

Below is my hierarchy.
Component hierarchy:
ExportPanel[name='exportPanel']
  javax.swing.JPanel[name=null]
    com.jidesoft.swing.CheckBoxList[name=null, selectedValues=[ Companies ], contents=[ Companies ,  Content ], selectionMode=SINGLE_SELECTION, enabled=true, visible=true, showing=true]
      javax.swing.CellRendererPane[,0,0,0x0,hidden]

I created my own FEST Extension to deal with this.

CheckListBoxFixture

This fixture allows me to define methods and encapsulate the interaction behaviours. It can’t be used generically and you will need to substitute the body of the click method to encapsulate your specific behaviours.

public class CheckListBoxFixture extends ContainerFixture<CheckBoxList> {

    public CheckListBoxFixture(Robot robot, CheckBoxList checkBoxList) {
        super(robot,checkBoxList);
    }


    public void click(String name) {
        //specific selection strategy
        @SuppressWarnings("unchecked")
        DefaultListModel<SelectorAction> model = (DefaultListModel<SelectorAction>)target.getModel();
        int index = 0;
        for(int i=0;i<model.size();i++) {
            if(model.elementAt(i).getViewNode().getDisplayName().equals(name)) {
                index=i;
                break;
            }
        }   

        //click the items
        target.getSelectionModel().addSelectionInterval(index, index);      
    } 

CheckListBoxExtension

I created a bespoke FEST extension.

public class CheckListBoxExtension extends ComponentFixtureExtension<com.jidesoft.swing.CheckBoxList, CheckListBoxFixture> {
    public static CheckListBoxExtension checkListBoxWithName(String name) {
        return new CheckListBoxExtension(name);
      } 
      private final String name;

      private CheckListBoxExtension(String name) {
        this.name = name;
      }  
      public CheckListBoxFixture createFixture(Robot robot, Container root) {
        CheckBoxList checkBoxList = robot.finder().findByName(root, name, CheckBoxList.class, true);
        return new CheckListBoxFixture(robot, checkBoxList);
      }
}

Usage

Then I use it.

I defined a name for my CheckListBox called viewsCheckListBox.

public ExportPanelDefinition selectView(final String viewName) {
    GuiActionRunner.execute(new GuiTask() {
        @Override protected void executeInEDT() throws Throwable {
            frame.with(CheckListBoxExtension.checkListBoxWithName("viewsCheckListBox")).click(viewName);
        }           
    });     
    return this;        
}}

EDT violation detected

I'm testing a pre-existing Swing application and I get these.
Exception in thread "Thread-10" org.fest.swing.exception.EdtViolationException: EDT violation detected
    at java.lang.Thread.getStackTrace(Unknown Source)
    at org.fest.util.StackTraces.stackTraceInCurrentThread(StackTraces.java:49)
    at org.fest.swing.edt.CheckThreadViolationRepaintManager.checkThreadViolations(CheckThreadViolationRepaintManager.java:78)
    at org.fest.swing.edt.CheckThreadViolationRepaintManager.addDirtyRegion(CheckThreadViolationRepaintManager.java:69)
    at org.fest.swing.edt.FailOnThreadViolationRepaintManager.addDirtyRegion(FailOnThreadViolationRepaintManager.java:31

SwingUtilities.invokeLater

Use SwingUtilities.invokeLater to update your components.

For me I found that some manual background threads were being created instead. I swapped these out.

SwingUtilities.invokeLater(new Runnable() {                 
    @Override public void run() {
        }
});

GuiActionRunner.execute

Use GuiActionRunner.execute to access your components.

GuiActionRunner.execute(new GuiTask() {
    @Override protected void executeInEDT() throws Throwable {
        //access your beautiful component       
    }

});
FEST Java