Unit Testing Swing Applications with Marathon/JavaDriver

One of the main reasons we desist from unit testing GUI code is that it is a hard problem. Marathon/JavaDriver provides a simple way of unit testing swing user interface components.

Introduction

Marathon/JavaDriver is a library to perform automatic Java application interaction, so Java programs can automatically perform the same interactions that user performs manually on the application. Marathon/JavaDriver is intended and primarily used as a backend for Marathon and MarathonITE GUI test automation tools. 

Unit testing GUI applications is a hard task. Not only do you need to perform the verifications needed, but also structure the code so that unit testing is simplified. Marathon/JavaDriver excels at unit testing swing applications.

 

In this article we provide a quick overview what JavaDriver is and the basic components of a JavaDriver test. We also review the Selenium/WebDriver bindings JavaDriver provides to simplify unit testing swing applications.

JavaDriver Architecture

JavaDriver is an implementation of Selenium/WebDriver that enables automating interactions for Java applications. It is based on client-server architecture. 

JavaDriver client includes:

  • The JavaDriver API that is used to create your WebDriver instance to develop tests.
  • The WebDriver's RemoteWebDriver class that actually handles the communication between the client and the application.

The JavaDriver server components include:

  • The JavaServer that is attached to the application using javaagent and implements the JSONWire protocol.
  • The JavaAgent that understands the commands and sends appropriate messages to the application's EventQueue.

When using JavaDriver for Unit Testing Swing applications, the Client application and the Server application resides in the same JVM. 

Unit Testing Swing Applications - Basic Steps

The following are the steps required for unit testing swing applications using JavaDriver. These steps apply to any test framework and application.

  • Display the JFrame/JDialog under test
    Typically you shall be using the test framework's @BeforeEach to display the dialog under test.
  • Create a JavaDriver instance.
  • Locate a Component on the dialog
    You will be using findElement or findElements with an appropriate By to get a reference to the component.
  • Perform action(s) on the Component
  • When you are unit testing swing (or any other GUI) applications, you typically perform the above two actions multiple times to mimic user interactions on the dialog.
  • Anticipate application response
    Add required waits, assertions and checkpoints to verify the expected behavior of the application.
  • Collect tests into suites and execute them
    All test frameworks provide ways to collect multiple tests into suites and execute them. Generate reports using the framework tools.

The Example Application

Login Dialog

We will test a very basic application in this article. Assume that your main application is gated by a login screen. The user should provide valid credentials to get into the application. In our test application, the Login process just displays a message (and we ignore that in our tests — more later).

LoginDialog Behavior

The LoginDialog is intentionally simple to demonstrate various JavaDriver functionalities. On entering valid credentials (that are hardcoded to bob/secret) a message is displayed and clicking OK terminates the application. If wrong credentials are entered, an error message is displayed and clicking OK clears the Username and Password fields. Clicking cancel displays a message and clicking OK terminates the application.

Unit Tests

We can think about multiple scenarios for this simple dialog.

  1. Success Case
    User enters valid credentials (username: bob, password: secret) and clicks on Login.
  2. Cancel
    User Cancels the operation by clicking on Cancel.
  3. Wrong Login
    User provides wrong credentials. In that case the username and password fields are cleared.

Notes on Unit Testing Swing GUI

Since the tests are executed in the same JVM as the application, we can’t have the application exiting.  For unit testing swing applications, we need to structure the source code so that testing a single dialog need not depend on multiple unrelated areas. A small change in the LoginDialog API provides the hooks required for overriding this behaviour:

  • onCancel
    You can override the behaviour of Cancel button by implementing this method.
  • onSuccess
    You can override the behaviour of Login button by implementing this method.
  • onInvalidCredentials
    You can override the behaviour of Login with invalid Username and Password button by implementing this method.
  • authenticate(String userName, String password)
    You can plug a custom authentication mechanism by overriding this method.

We just provided API to override methods to achieve testability for the LoginDialog class. In regular projects, development teams use a technique called Dependency Injection. Various libraries like Spring and Guice facilitates easy decoupling of client from the service objects.

Showing the User Interface

Displaying the JFrame or JDialog under test is the first thing that we need to do before we can access the dialog components.

We used an anonymous class to override the functionality of the LoginDialog in the above code snippet. Alternatively, we can create a Class that extends the LoginDialog and override the methods.

Once the login object is created we display it using setVisible method. Keep in mind that we can access UI components only from EDT – using  SwingUtitlities#invokeLater method.

Creating a JavaDriver Instance

The JavaDriver implements the WebDriver interface. We use the WebDriver interface to access Selenium/WebDriver methods and access other objects. Instantiating the JavaDriver is required before we can access components from the dialog.

We create an instance of a WebDriver instance using the JavaDriver constructor. 

Once we create an instance of the JavaDriver class, we use this instance to invoke methods and access other interfaces. We do this by assigning the instance to a variable and using that variable to call methods.

The following example creates a JavaDriver object and assigns it to a variable named driver.

We create a JavaProfile and set it to use EMBEDDED mode and for SWING_APPLICATION. Pass this profile to the JavaDriver constructor to create an object and assign it to driver.

The default constructor of JavaDriver creates with the same JavaProfile we created above. So we could have just created a JavaDriver object without passing the profile object for unit tessting Swing applications.

Locating Components on the Dialog

Before we can interact with our dialog we first need to locate the components on the dialog and then we can perform actions like sending keys or clicking a mouse. We locate components using various By object instances. Marathon JavaDriver supports finding elements by using id, name, tagName, className and cssSelector. We explain them below:

ID and Name

When using JavaDriver with Swing applications, both ID and Name maps to the value set by using JComponent#setName method.

Tag Name

JavaDriver creates the tag names from the name of the closest Java/Swing or AWT class. The logic for creating a tag name is to remove the package name and initial 'J' character and convert from CamelCase to lower-case words separated by hyphens. So a tag name for javax.swing.JTextField is text-field.

Class Name

You can use the fullly qualified class name to find a Java/Swing component.

CSS Selector

The most generic and powerful By clause supported by Marathon JavaDriver is css selector. The CSS selector syntax is same is for regular CSS used by browsers. More details will be given in another article.

Performing Actions on the Component(s)

Once we’ve identified the Swing Components that we want use in our test, the next step is to interact with them. We perform actions on a Swing component by using the action methods on an instance of the WebElement interface returned by one of our findElement methods. The JavaDriver supports the following WebElement interface methods to interact with a Component:
  • The sendKeys method to enter text
  • The clear method to remove any entered text.
  • The click method to click on a component

Verifying the Application Response

Once we performed actions on the application, we expect some way of checking the results. Either the results are provided by the application object(s) or some change in the state of the application dialog.

Accessing Component State

You can use WebElement‘s getText and getAttribute methods to access a components’ state. Specifically, the getAttribute method allows you to access any bean value from the component. For example, component.toolTipText returns the tooltip provided for a component.

Waiting for a Response

When you perform some actions, the application may change the state of the UI. For example, the Login button might get enabled only when both the Username and Password fields are filled up. When manually using the application, we wait till the Login button is enabled before clicking on it. Selenium/WebDriver supports implicit and explicit waits so that the test code can wait till a state change occurs.

Implicit Waits

We set a fixed elapsed time that applies to all driver interactions using implicit wait. Implicit wait effects not only the interactions but also the wait time for finding elements.

The above statement requests the driver to set the implicit wait to 10 seconds.

Explicit Waits

Explicit waits wait until an expected condition occurs on the application dialog, or until a maximum wait time elapses. To use an explicit wait, you create an instance of the WebDriverWait class with a maximum wait time, and you invoke its until method with an expected condition.

The WebDriver API provides an ExpectedConditions class with methods for various standard types of expected conditions. The retuned expected condition objects can directly passed to the until method. You can also pass your own Function objects that return true/non-null value when the expected condition is met and false otherwise. The until method checks repeatedly, until the maximum wait time elapses, for a true boolean return value or a non-null object reference, as an indication that the expected condition has occurred.

The above example waits till the loginBtn is clickable or 10 seconds whichever is earlier.

Putting it All Together

By putting all the pieces together we can create a complete test case.

We use JUnit4 as the test harness for this example. You can use any other testing framework with JavaDriver.

The Fixture

Displaying the JFrame or JDialog under test is the first thing to be done. Assuming that your all tests in your test class exercises a single Dialog you can use the Fixtures to show the dialog and close it at the end.

 You need to start the UI in the Swing Event Dispatcher Thread (EDT). Once that is done, you can create the driver.

The Test

The test is intentionally simple to show the basic functions that are available.

The major task for a test is to identify the components on the dialog. JavaDriver supports css selector. In this case, we are just getting the user, pass and loginBtn using the tag-name ( and attribute in case of loginBtn). sendKeys() and click() does what you expect them to do. Finally, an assertion that the login is succeeded is performed using JUnit assertTrue method.

Resources

Close Menu