Introduction

Swing is a powerful and widely used GUI toolkit for Java applications, offering a rich set of components for creating desktop applications. When developing Swing applications, testing becomes a crucial aspect to ensure that the graphical user interface (GUI) functions as intended and remains robust throughout the development lifecycle. In this article, we will explore various techniques and tools for testing Swing applications, accompanied by code examples.

Unit Testing with JUnit

Unit testing is a fundamental practice in software development to ensure that individual components of an application work correctly in isolation. JUnit is a popular testing framework for Java, and it can be effectively used for testing Swing components.

Let’s consider a simple Swing application with a JFrame containing a JButton. We want to test that clicking the button triggers the intended action.

java
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MySwingApp extends JFrame {
private JButton myButton;

public MySwingApp() {
super(“Swing App Example”);
myButton = new JButton(“Click Me”);

myButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(MySwingApp.this, “Button Clicked!”);
}
});

getContentPane().add(myButton);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
}

public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
MySwingApp app = new MySwingApp();
app.setVisible(true);
});
}
}

Now, let’s write a JUnit test for the MySwingApp class:

java
import org.junit.jupiter.api.Test;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import java.awt.Robot;
import java.awt.event.InputEvent;
import static org.junit.jupiter.api.Assertions.*;

class MySwingAppTest {

@Test
void testButtonClick() {
MySwingApp app = new MySwingApp();
app.setVisible(true);

JButton button = (JButton) TestUtils.getChildNamed(app, “Click Me”);

assertNotNull(button);

// Simulate a button click using Robot class
Robot robot = TestUtils.createRobot();
robot.mouseMove(button.getLocationOnScreen().x + button.getWidth() / 2,
button.getLocationOnScreen().y + button.getHeight() / 2);
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);

// Wait for the UI to update
TestUtils.waitForSwing();

// Check if the JOptionPane is displayed
assertTrue(TestUtils.isDialogShowing(JOptionPane.class));

// Close the application
app.dispose();
}
}

In this example, we use a utility class TestUtils to interact with Swing components programmatically. The createRobot method initializes a Robot object, and waitForSwing ensures that the Swing event dispatch thread has processed all pending events.

Integration Testing with FEST-Swing

While JUnit is suitable for unit testing, integration testing involves testing the interaction between different components and ensuring that the entire application behaves as expected. FEST-Swing is a testing library specifically designed for testing Swing applications at the integration level.

Let’s modify the previous example to include an additional JTextField and a JLabel. The JLabel will display a message based on the input from the JTextField.

java
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MySwingApp extends JFrame {
private JButton myButton;
private JTextField textField;
private JLabel resultLabel;

public MySwingApp() {
super(“Swing App Example”);

myButton = new JButton(“Click Me”);
textField = new JTextField(10);
resultLabel = new JLabel();

myButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String input = textField.getText();
resultLabel.setText(“Hello, “ + input + “!”);
}
});

GroupLayout layout = new GroupLayout(getContentPane());
getContentPane().setLayout(layout);

layout.setAutoCreateGaps(true);
layout.setAutoCreateContainerGaps(true);

layout.setHorizontalGroup(layout.createSequentialGroup()
.addComponent(textField)
.addComponent(myButton)
.addComponent(resultLabel));

layout.setVerticalGroup(layout.createParallelGroup()
.addComponent(textField)
.addComponent(myButton)
.addComponent(resultLabel));

setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
}

public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
MySwingApp app = new MySwingApp();
app.setVisible(true);
});
}
}

Now, let’s write an integration test using FEST-Swing to verify that the label updates correctly when the button is clicked:

java
import org.fest.swing.fixture.FrameFixture;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.fest.swing.edt.GuiActionRunner.execute;
import static org.junit.jupiter.api.Assertions.assertEquals;

class MySwingAppIntegrationTest {

private FrameFixture window;

@BeforeEach
void setUp() {
MySwingApp app = execute(() -> new MySwingApp());
window = new FrameFixture(app);
window.show();
}

@AfterEach
void tearDown() {
window.cleanUp();
}

@Test
void testButtonClickUpdatesLabel() {
window.textBox(“textField”).enterText(“John”);
window.button(“Click Me”).click();
assertEquals(“Hello, John!”, window.label().text());
}
}

In this example, we use FEST-Swing’s FrameFixture to interact with the Swing components. The test ensures that entering a name in the textField and clicking the button updates the resultLabel accordingly.

End-to-End Testing with Selenium and TestFX

For end-to-end testing of Swing applications, especially those with embedded web components, Selenium and TestFX can be valuable tools. Selenium is widely used for web application testing, and TestFX is designed for testing JavaFX applications, but it can also be adapted for Swing applications.

Let’s consider a Swing application that embeds a JavaFX WebView to display web content. We want to test that navigating to a URL in the WebView works as expected.

java
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import javax.swing.*;
import java.awt.*;

public class WebSwingApp extends JFrame {

public WebSwingApp() {
super(“Web Swing App Example”);

JFXPanel jfxPanel = new JFXPanel();
WebView webView = new WebView();
jfxPanel.setScene(new Scene(webView));

JTextField urlTextField = new JTextField(20);
JButton goButton = new JButton(“Go”);

goButton.addActionListener(e -> {
String url = urlTextField.getText();
webView.getEngine().load(url);
});

GroupLayout layout = new GroupLayout(getContentPane());
getContentPane().setLayout(layout);

layout.setAutoCreateGaps(true);
layout.setAutoCreateContainerGaps(true);

layout.setHorizontalGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup()
.addComponent(jfxPanel)
.addGroup(layout.createSequentialGroup()
.addComponent(urlTextField)
.addComponent(goButton))));

layout.setVerticalGroup(layout.createSequentialGroup()
.addComponent(jfxPanel)
.addGroup(layout.createParallelGroup()
.addComponent(urlTextField)
.addComponent(goButton)));

setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
}

public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
WebSwingApp app = new WebSwingApp();
app.setVisible(true);
});
}
}

Now, let’s write an end-to-end test using Selenium and TestFX to verify the functionality of the embedded WebView:

java
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import static org.junit.jupiter.api.Assertions.assertEquals;

class WebSwingAppE2ETest {

private WebDriver driver;

@BeforeEach
void setUp() {
System.setProperty(“webdriver.chrome.driver”, “path/to/chromedriver”);
driver = new ChromeDriver();
}

@AfterEach
void tearDown() {
driver.quit();
}

@Test
void testWebViewNavigation() {
driver.get(“file:///path/to/WebSwingApp.html”);

WebElement urlTextField = driver.findElement(By.id(“urlTextField”));
WebElement goButton = driver.findElement(By.id(“goButton”));
WebElement webView = driver.findElement(By.id(“webView”));

urlTextField.sendKeys(“https://www.example.com”);
goButton.click();

// Wait for the WebView to load
TestUtils.waitFor(() -> webView.getAttribute(“src”).equals(“https://www.example.com”));

assertEquals(“Example Domain”, driver.getTitle());
}
}

In this example, we use Selenium to control a Chrome WebDriver and TestFX to interact with the JavaFX components. The test verifies that entering a URL in the urlTextField and clicking the goButton loads the expected content in the embedded WebView.

Conclusion

Testing Swing applications is essential to ensure the reliability and correctness of the graphical user interface. Unit testing with JUnit, integration testing with FEST-Swing, and end-to-end testing with Selenium and TestFX provide a comprehensive testing strategy for Swing applications. By combining these approaches, developers can achieve a high level of confidence in the functionality and robustness of their Swing applications. Remember to adapt these examples to the specifics of your application and leverage the testing tools and libraries available in the Java ecosystem.