Testing a Swing app with Jemmy

Now that we've set up the basic tools, we'll need to navigate to the components we really want to test. So, let's go test some buttons.

public void testButton(){
 getTabbedPaneOperator().selectPage("Buttons");
 JButtonOperator jbo = new JButtonOperator(getOuterFrameOperator(), 0);
 assertTrue(jbo.getText().equals("One"));
 // get the JButton the JButtonOperator refers to and set its action
 ((JButton)jbo.getSource()).setAction(new SimpleButtonAction((JButton)jbo.getSource()));
 assertTrue(jbo.getText().equals("Action Set")); // tells us that the action has been set correctly and that // the buttons text has been changed successfully
 jbo.push();
 // push()is a blocking method. if the action triggered a modal dialog we would use .pushNoBlock();
 assertTrue(jbo.getText().equals("I've been clicked"));
 // click it again to reset it to the original state
 jbo.push();
 assertTrue(jbo.getText().equals("One"));
 }


The first thing we need to do is select the appropriate tab. JTabbedOperator refers to the tabs as "pages." After calling ourGetTabbedPaneOperator() method, we call selectPage("Buttons") on it because "Buttons" is the title of the tab we want. Behind the scenes, Jemmy finds the tab in question and executes a mouse click to bring it to select it.

Java Click To expand

Next up we select the first button with:

JButtonOperator jbo = new JButtonOperator(getOuterFrameOperator(), 0);


If we had passed in a 1 instead of the 0, we would have gotten the second button. Since we're going for the first button, we could have actually just left that second parameter off entirely. If you're not specific (and this applies to all operators,) Jemmy will give you the first instance of the type of object you're looking for. If for some reason Jemmy can't find the object you specify in any constructor, it will throw an exception. Which, while an atypical way of handling such a situation, helps us to minimize the code in our test since we won’t need to bother testing if the constructor worked (which, if we make it to the next line of code, it did.) Now that we've gotten the button, we should make sure it's the right one. Our JButtonOperator extends AbstractButtonOperator which has a bunch of methods that just pass through to the underlying button object, getText() being a prime example. We know that the button we're hoping we have has "One" as its text. So, we can just put that in a standard JUnit assertion.

assertTrue(jbo.getText().equals("One"));


As mentioned before, you don't normally deal with the actual underlying Swing object directly, but sometimes you need to. In this case we do because the buttons in the SwingSet don't actually do anything by default. This makes it difficult to test because clicking on them did the appropriate thing. So, we're going to give our button an action to perform; first, though, we need to get the actual JButton object:

((JButton)jbo.getSource()).setAction(new SimpleButtonAction((JButton)jbo.getSource()));


The getSource() method of an Operator will get you the underlying Swing object. Once we get it, we then assign it a new SimpleButtonAction as its action. SimpleButtonAction is an action written for this test that just changes the text of a button when you click on it. Calling setAction(...) on a JButton also sets the text of the button. If our action assignment goes well, then the text of the button will be "Action Set", which leads us to our next assertion:

assertTrue(jbo.getText().equals("Action Set"));


With that in place, we need to "push" the button to see if it is functioning correctly. You can "press" a button or you can "push" it. Pressing is equivalent to executing a mouse down. Pushing is the same as clicking. Nothing special happens when our button is pressed, so we won't bother testing that. Pushing it couldn't be simpler.

jbo.push()


If our action worked correctly, the button’s text will have changed to "I've been clicked".

assertTrue(jbo.getText().equals("I've been clicked"));


Now, just to make sure that our button hasn't gotten itself into some funky state as a result of our pressing it, let's press it again to make sure it's still working.

jbo.push();
assertTrue(jbo.getText().equals("One"));


Clicking again should reset the text to "One", and if our assertion passes, it has. Next, let's test a JTable.

public void testTable(){
 getTabbedPaneOperator().selectPage("TableView");
 JTableOperator jto = new JTableOperator(getOuterFrameOperator());
 TableModel tm = jto.getModel();
 assertTrue(tm.getRowCount() == 32);
 assertTrue(tm.getColumnCount() == 6);
 assertTrue(String.valueOf(jto.getValueAt(0,0)).equals("Mike"));
 jto.selectCell(0,0);
 assertTrue(((String)jto.getValueAt(jto.getSelectedRow(),jto.getSelectedColumn())).equals("Mike"));
 }


The JTable we're testing has 32 rows and 6 columns. The first cell should have a value of "Mike". With these things in mind we can proceed to test the table.

Java Click To expand

You'll notice that we leave out the int parameter when we instantiate our JTableOperator. There's only one in this tab, so we don't have to worry about providing any parameters other than a starting point. The contents of the table are, of course, stored in its TableModel, so we call one of JTableOperators pass-through methods to get that:

TableModel tm = jto.getModel();


Afterwards, we just go through a short series of assertions to confirm that the table we got was the table we were expecting. The only thing left to test is that we are able to correctly select a cell, which we do with the selectCell(int, int) method. The ints being the indexes into the rows and columns of the cell we want selected.

jto.selectCell(0,0); assertTrue(((String)jto.getValueAt(jto.getSelectedRow(),jto.getSelectedColumn())).equals("Mike"));


The next test, testDialog(), will be testing a JDialog by way of a JMenuItem. Selecting menuItems is trickier than it really ought to be. This is partially due to the fact that Swing’s JMenu is actually a subclass of JmenuItem, which is amazingly counterintuitive and problematic, because when you try and get Jemmy to find a JmenuItem, it will try and give you the first one it finds, (which is actually a Jmenu.) To complicate matters even more, the JMenuItems aren't actually contained in the JMenu—they're in its JPopupMenu. Fortunately, Jemmy’s developers have given us some convenience methods to make our lives a bit easier.

Java Click To expand
public void testDialog(){
 JFrameOperator jfo = getOuterFrameOperator();
 JMenuBarOperator jmbo = new JMenuBarOperator(jfo); // there's only one menubar
 jmbo.pushMenu("File/About", "/");
 JDialogOperator jdo = new JDialogOperator("About Swing!");
 JButtonOperator jbo = new JButtonOperator(jdo, "OK");
 jbo.push(); // close the dialog
 jmbo.pushMenu("File");
 // select the about menu item
 JMenuOperator jmo = new JMenuOperator(jmbo, "File");
 //jmo.pushMenu("File/About", "/"); // would work here too. JPopupMenu jpm = jmo.getPopupMenu();
 JMenuItem firstMenuItem = (JMenuItem)jpm.getComponent(0);
 assertTrue(firstMenuItem.getText().equals("About"));
 JMenuItemOperator jmio = new JMenuItemOperator(firstMenuItem); jmio.push();
 jdo = new JDialogOperator("About Swing!");
 jbo = new JButtonOperator(jdo, "OK");
 jbo.push();
 jmbo = new JMenuBarOperator(jfo);
 jmbo.pushMenu("File");
 jmo = new JMenuOperator(jmbo, "File");
 jmo.pushKey(KeyEvent.VK_T);
 jdo = new JDialogOperator("About Swing!");
 jbo = new JButtonOperator(jdo, "OK");
 jbo.push();
 /*from the docs: http://jemmy.netbeans.org/faq.html#awtmenu
 * * The only way [to interact with AWT menus] is to use keyboard shortcuts, or * Alt key sequences (using [Alt], arrows and [Enter]).
 */
 }


You access a menu by selecting the menu bar, which is always visible, then pushing a menu that you specify by its title. There are quite a few ways to do that, so we'll go through three of them here. After each push we'll have to close the resulting dialog box so that we can get back to the menu again. We'll cover the dialog bit after discussing the ways to open it. The simplest way is to just call its pushMenu(String, String) method. The first string is the path to the menu you want to click on, including all of its submenus. The second tells it what delimiter you used in the preceding string. So, in our example we'll use the following lines:

JMenuBarOperator jmbo = new JMenuBarOperator(jfo); jmbo.pushMenu("File/About", "/");
// or…
jmbo.pushMenuNoBlock("File/About", "/");


If we were opening a modal dialog, we'd have to use the NoBlock variant. This is because unless otherwise specified, all Jemmy operations are blocking. If we don't use a non-blocking variant with a modal dialog, the thread will get caught up waiting for the dialog to go away and time out. Whenever a Jemmy operation times out it will throw an org.netbeans.jemmy.TimeoutExpiredException exception (we’ll cover timeouts later), which will cause your test to fail, which is what you want when things take longer than they should. In the following examples we won't be using the NoBlock variants because this particular dialog isn't modal. The next way is to actually drill our way down to the menu item we want manually and then click it.

JMenuOperator jmo = new JMenuOperator(jmbo, "File");
//jmo.pushMenu("File/About", "/"); // would work here too. JPopupMenu jpm = jmo.getPopupMenu();
JMenuItem firstMenuItem = (JMenuItem)jpm.getComponent(0);
assertTrue(firstMenuItem.getText().equals("About"));
JMenuItemOperator jmio = new JMenuItemOperator(firstMenuItem); // the first menu item is the "About" menu, which is the one we want. jmio.push();


In this example we've instantiated the JMenuItem by passing it an actual Swing object. Doing it that way might be atypical, but sometimes it’s a useful way of instantiating an Operator class. It is worthwhile to note that JMenuOperators also have a pushMenu(String, String) method just like the JmenuBarOperator, but this example is about drilling down to the actual menu item we want to click on, so we’ll do it the long way. The “About” menu item is in the JPopupMenu in the "File" JMenu. We’ll use a typical Jemmy constructor to find that, then use it's getPopupMenu() method to get the JpopupMenu, and then grab the first component out of that because the "About" menu is the first one. After verifying that we have the correct menu item, we now instantiate a JmenuItemOperator so that we can actually perform operations (like clicking on it.) Then it's just a matter of calling push() or pushNoBlock(). The last way we'll explore is the use of keystrokes to select the menu item. It's important to know how to do this so that your menuitems have the appropriate key commands associated with them. You'll also need to know this if you're using AWT menus because Jemmy can't find mouse coordinates for AWT menus and thus can't click on them. In the SwingSet, pressing Alt+f doesn't always activate the File menu even though it has that key binding, which is a great example of why you should have tests like this. If the SwingSet's creators had tested navigating menus with key commands, they would have noticed that sometimes it does one thing ( the "Paint Focus" action) and sometimes it does another (opening the File menu). Anyway, we'll click on the File menu to get it in focus, and then we'll execute a java.awt.KeyEvent on it. In this case, we're just typing T, which is bound to the About menu item. We could have also typed a down arrow, followed by the Enter key.

jmbo = new JMenuBarOperator(jfo);
jmbo.pushMenu("File");
jmo = new JMenuOperator(jmbo, "File");
jmo.pushKey(KeyEvent.VK_T);


And now, as promised, we'll deal with that dialog box. It's pretty simple, and if you've looked at that part of the code already, you've probably figured it out.

JDialogOperator jdo = new JDialogOperator("About Swing!");
JButtonOperator jbo = new JButtonOperator(jdo, "OK");
jbo.push();


You instantiate a JdialogOperator, just like a JframeOperator, by passing it the title of the window you want. The dialog contains an OK button, which we grab by specifying its text. We could have specified its index, or not specified anything at all, because this is the only JButton in this window and Jemmy’s default behavior is to give you the first one it finds. Clicking this OK button will close the dialog. Closing that dialog brings us to the end of our example code. There are, however, a few specific Jemmy things we still need to cover. The first of which is Timeouts. Assuming you're not running these tests on an 8086, you haven't had any timeouts, so increasing the timeout hasn't been needed. Also, none of the tests we ran resulted in time-consuming operations, but sometimes a click can be telling the system to go off and perform some massive calculation that will just take a bit longer than most. Timeouts are handled by the org.netbeans.jemmy.Timeouts class, which is essentially just a wrapper around a HashTable. In order to set a timeout, you have to know its key. Unfortunately, those keys don’t appear to be documented in any central location, so you need to look in the javadocs of the class in which you need to modify a timeout. Each class that has particular timeouts generally has them listed and explained. To make your life a bit easier, we’ve included a list of the documented timeouts in the current version of Jemmy (see below). The easiest way to set a timeout is by calling this method:

JemmyProperties.setCurrentTimeout(String arg0, long arg1);


If you want to set a number of timeouts at once, you can call this method:

JemmyProperties.setCurrentTimeouts(Timeouts arg0);


Timeouts can be stored in a properties file and loaded via the Timeouts.load(...) and Timeouts .loadDefaults(...) methods which will make your life much easier if you need to use a common set in multiple tests. You can set a timeout globally or locally via Timeouts’ setDefault(String name, long newValue) and setTimeout(String name, long newValue) methods, respectively. ComponentOperator has a setTimeouts(Timeouts) method which is inherited by all of its children. Unfortunately, you have to instantiate an object, which causes it to try an find the swing object in question before you can have access to its setTimeouts(Timeouts) method. The easiest way is to just set the timeout globally before you instantiate the Operator you need and then set it back afterwards. The following list contains the timeouts we were able to find by digging through the source code. It should not be considered exhaustive. Don't let its size intimidate you, because you rarely ever use them, and when you do need one you can just pull up the Javadocs for that class.

The last issue to cover in Jemmy is how to suppress its rather verbose output to Standard Out. The FAQ indicates that it’s generated by the org.netbeans.jemmy.Test class, but it actually seems to be coming from the TestOut class. Luckily, suppressing it is very simple. Just call add the following line to the setUp() method of your or anywhere else that is executed before you start using Jemmy objects.

JemmyProperties.getProperties().setOutput(TestOut.getNullOutput());


JemmyProperties and TestOut are both in the org.netbeans.jemmy package; if you only want to suppress some of the output, TestOut has a variety of constructors that will let you control exactly where all that output will go.

That's it! There are of course a variety of tests you can perform on Swing object with Jemmy that we don't cover here, as doing so would take up an entire tutorial in itself. But hopefully what we've covered here will be enough to get you moving.


JaVa
   
Comments