-
Notifications
You must be signed in to change notification settings - Fork 10
Testing Java Swing applications
In our Java testing library there is a possibility to test GUI applications based on Swing library. Testing of GUI applications written in Python is not yet supported. Testing is mainly based on the AssertJ library, which can simulate user behavior.
All examples are given from Text Editor
project.
The process for testing Swing applications is as follows:
- Create an object (that extends JFrame) in the constructor of the test class.
- Define objects marked with
@SwingComponent
annotation. - Create tests as methods marked with
@DynamicTest
annotation. - Testing. The tests test the same object created in the constructor. So, it's important to note that the next test case will start with the object state where the previous test case ended. For example, if the 8th test opens a dialog box and checks that it has been opened and ends, the 9th test will start when the dialog box is already open. Or 4th test writes something in the text field then 5th test starts with this text field already being filled. This should be kept in mind.
The object to be tested should be the main window of the application. For example, for the next student program:
package editor;
import javax.swing.*;
public class TextEditor extends JFrame {
public TextEditor() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(300, 300);
setVisible(true);
setLayout(null);
}
}
The constructor of the test class should look as follows:
import editor.TextEditor;
import org.hyperskill.hstest.stage.SwingTest;
import org.hyperskill.hstest.testcase.CheckResult;
import org.hyperskill.hstest.testcase.TestCase;
public class EditorTest extends SwingTest<Attach> {
public EditorTest() {
super(new TextEditor());
}
}
You can access the FrameFixture
object with the window
variable and the original JFrame
object with the frame
variable. These variables are defined in the SwingTest
class, so you do not need to create them yourself.
There is an annotation called @SwingComponent
. You can import it like the following:
import org.hyperskill.hstest.testing.swing.SwingComponent;
You can mark assertj-swing's fixtures with this annotation and hs-test
library will search the appropriate component in the object's tree. The following components are supported:
JButtonFixture
JCheckBoxFixture
JComboBoxFixture
DialogFixture
JFileChooserFixture
JInternalFrameFixture
JLabelFixture
JListFixture
JMenuItemFixture
JPanelFixture
JProgressBarFixture
JRadioButtonFixture
JScrollBarFixture
JScrollPaneFixture
JSliderFixture
JSpinnerFixture
JSplitPaneFixture
JTabbedPaneFixture
JTableFixture
JTextComponentFixture
JToggleButtonFixture
JToolBarFixture
JTreeFixture
You can import these components by the following path:
import org.assertj.swing.fixture.*;
All these fixtures represent the components without Fixture
at the end that are located in the following path:
import javax.swing.*;
The library hs-test
will find a component with an appropriate type and name that equals to the variable name capitalized by the first letter. For example, the following field:
@SwingComponent private JButtonFixture saveButton;
will represent the component with type JButton
and name SaveButton
. You can set the name to the component by using .setName(String)
method. You do not need to initialize this field, the hs-test
library will do it automatically before running the tests. If the library doesn't find such a component, it will show an error to the user with appropriate feedback. You can use these fixtures to access the components of the user program and do real tests. You can look at the AssertJ documentation to see what and how you can test in the user program.
The stage description should specify how to name certain components of the program so that they can be accessed from the tests. So, a stage description should contain something like this:
Due to testing reasons, you need to set name to some components.
Set the names to these components:
- JTextArea component to "TextArea"
- Field which contains filename to "FilenameField"
- Button that saves the file to "SaveButton"
- Button that loads a file to "LoadButton"
- ScrollPane to "ScrollPane"
You can customize the name of the component by providing name
parameter to the annotation. Example:
@SwingComponent(name = "save") private JButtonFixture saveButton;
will represent the component with type JButton
and name save
.
Tests are methods defined with @DynamicTest
annotation. You can see this page how to use such annotation.
One addition though: the library assertj-swing provides useful assertions like fixture.requireEmpty()
or fixture.requireEnabled()
and you can't provide feedback to them because if the check fails, the AssertionError
will be thrown. The library will catch such an error and rethrow WrongAnswer without feedback, so you can use such assertions but no feedback isn't good. For this reason, you can use feedback
parameter inside the @DynamicTest
annotation.
Example:
@DynamicTest(feedback = "SaveButton should be enabled!")
CheckResult test1() {
saveButton.requireEnabled();
return correct();
}
In this example, a SaveButton should be enabled!
feedback will be shown in case saveButton
is not enabled.
There are some helper methods to check multiple components without such a hassle and within a single test:
requireEnabled(Fixture...)
requireDisabled(Fixture...)
requireVisible(Fixture...)
requireNotVisible(Fixture...)
requireFocused(Fixture...)
requireEditable(Fixture...)
requireNotEditable(Fixture...)
requireEmpty(Fixture...)
Example:
@DynamicTest
CheckResult test1() {
requireEnabled(saveButton, loadButton, checkBox, textField);
requireVisible(saveButton, loadButton, checkBox, textField);
requireEmpty(textField);
requireNotEditable(textField);
return correct();
}
Here's full example of tests in the 3rd stage of the Text Editor
project.
import editor.TextEditor;
import org.assertj.swing.fixture.JButtonFixture;
import org.assertj.swing.fixture.JMenuItemFixture;
import org.assertj.swing.fixture.JScrollPaneFixture;
import org.assertj.swing.fixture.JTextComponentFixture;
import org.hyperskill.hstest.dynamic.DynamicTest;
import org.hyperskill.hstest.stage.SwingTest;
import org.hyperskill.hstest.testcase.CheckResult;
import org.hyperskill.hstest.testing.swing.SwingComponent;
import org.junit.After;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import static org.hyperskill.hstest.testcase.CheckResult.correct;
public class EditorTest extends SwingTest {
public EditorTest() {
super(new TextEditor());
}
@SwingComponent JTextComponentFixture textArea;
@SwingComponent JTextComponentFixture filenameField;
@SwingComponent JButtonFixture saveButton;
@SwingComponent JButtonFixture loadButton;
@SwingComponent JScrollPaneFixture scrollPane;
@SwingComponent JMenuItemFixture menuFile;
@SwingComponent JMenuItemFixture menuLoad;
@SwingComponent JMenuItemFixture menuSave;
@SwingComponent JMenuItemFixture menuExit;
String filename1 = "SomeFile.txt";
String filename2 = "AnotherFile.txt";
String noExistFile = "FileDoesNotExist";
String textToSave1 = "Basic text editor\nType here too\nHere also\n\n";
String textToSave2 = " Sonnet I\n" +
" \n" +
" \n" +
"FROM fairest creatures we desire increase,\n" +
"That thereby beauty's rose might never die,\n" +
"But as the riper should by time decease,\n" +
"His tender heir might bear his memory:\n" +
"But thou, contracted to thine own bright eyes,\n" +
"Feed'st thy light'st flame with self-substantial fuel,\n" +
"Making a famine where abundance lies,\n" +
"Thyself thy foe, to thy sweet self too cruel.\n" +
"Thou that art now the world's fresh ornament\n" +
"And only herald to the gaudy spring,\n" +
"Within thine own bud buriest thy content\n" +
"And, tender churl, makest waste in niggarding.\n" +
"Pity the world, or else this glutton be,\n" +
"To eat the world's due, by the grave and thee.\n" +
"\n" +
" Sonnet II \n" +
"\n" +
"\n" +
"When forty winters shall beseige thy brow,\n" +
"And dig deep trenches in thy beauty's field,\n" +
"Thy youth's proud livery, so gazed on now,\n" +
"Will be a tatter'd weed, of small worth held:\n" +
"Then being ask'd where all thy beauty lies,\n" +
"Where all the treasure of thy lusty days,\n" +
"To say, within thine own deep-sunken eyes,\n" +
"Were an all-eating shame and thriftless praise.\n" +
"How much more praise deserved thy beauty's use,\n" +
"If thou couldst answer 'This fair child of mine\n" +
"Shall sum my count and make my old excuse,'\n" +
"Proving his beauty by succession thine!\n" +
"This were to be new made when thou art old,\n" +
"And see thy blood warm when thou feel'st it cold.";
@DynamicTest
CheckResult test1() {
requireEditable(textArea);
requireEmpty(textArea, filenameField);
requireEnabled(saveButton, loadButton);
return correct();
}
@DynamicTest(feedback = "Can't enter multiline text in TextArea.")
CheckResult test2() {
textArea.setText(textToSave1);
textArea.requireText(textToSave1);
textArea.setText("");
textArea.setText(textToSave2);
textArea.requireText(textToSave2);
return correct();
}
@DynamicTest(feedback = "Can enter multiline text in FilenameField, but shouldn't")
CheckResult test3() {
String text = textToSave1;
filenameField.setText(text);
filenameField.requireText(text.replace("\n", " "));
filenameField.setText("");
return correct();
}
@DynamicTest(feedback = "Text in FilenameField and in TextArea " +
"should stay the same after saving file")
CheckResult test4() {
filenameField.setText(filename1);
textArea.setText(textToSave1);
saveButton.click();
filenameField.requireText(filename1);
textArea.requireText(textToSave1);
return correct();
}
@DynamicTest(feedback = "Text in FilenameField and in TextArea " +
"should stay the same after saving file")
CheckResult test5() {
String text = textToSave2;
String file = filename2;
filenameField.setText(file);
textArea.setText(text);
saveButton.click();
filenameField.requireText(file);
textArea.requireText(text);
filenameField.setText("");
textArea.setText("");
return correct();
}
@DynamicTest(feedback = "Text in FilenameField stay the same after loading file")
CheckResult test6() {
String file = filename1;
filenameField.setText(file);
textArea.setText("");
loadButton.click();
filenameField.requireText(file);
filenameField.setText("");
textArea.setText("");
return correct();
}
@DynamicTest(feedback = "Text should be the same after saving and loading same file")
CheckResult test7() {
String[] texts = {textToSave2, textToSave1};
String[] files = {filename1, filename2};
for (int i = 0; i < 2; i++) {
String text = texts[i];
String file = files[i];
filenameField.setText("");
textArea.setText("");
filenameField.setText(file);
textArea.setText(text);
saveButton.click();
filenameField.setText("");
textArea.setText("");
filenameField.setText(file);
loadButton.click();
textArea.requireText(text);
}
return correct();
}
@DynamicTest(feedback = "TextArea should be empty if user tries to " +
"load file that doesn't exist")
CheckResult test8() {
textArea.setText(textToSave1);
filenameField.setText(noExistFile);
loadButton.click();
textArea.requireText("");
return correct();
}
@DynamicTest(feedback = "TextArea should correctly save and load an empty file")
CheckResult test9() {
textArea.setText("");
filenameField.setText(filename1);
saveButton.click();
textArea.setText(textToSave2);
loadButton.click();
textArea.requireText("");
return correct();
}
// menu-related tests
@DynamicTest
CheckResult test10() {
requireEnabled(menuLoad, menuSave, menuFile, menuExit);
return correct();
}
@DynamicTest(feedback = "Text in FilenameField and in TextArea " +
"should stay the same after saving file using MenuSave")
CheckResult test11() {
filenameField.setText(filename1);
textArea.setText(textToSave1);
menuSave.click();
filenameField.requireText(filename1);
textArea.requireText(textToSave1);
return correct();
}
@DynamicTest(feedback = "Text in FilenameField and in TextArea " +
"should stay the same after saving file using MenuSave")
CheckResult test12() {
String text = textToSave2;
String file = filename2;
filenameField.setText(file);
textArea.setText(text);
menuSave.click();
filenameField.requireText(file);
textArea.requireText(text);
filenameField.setText("");
textArea.setText("");
return correct();
}
@DynamicTest(feedback = "Text in FilenameField stay " +
"the same after loading file using MenuLoad")
CheckResult test13() {
String file = filename1;
filenameField.setText(file);
textArea.setText("");
menuLoad.click();
filenameField.requireText(file);
filenameField.setText("");
textArea.setText("");
return correct();
}
@DynamicTest(feedback = "Text should be the same after saving " +
"and loading same file using MenuLoad")
CheckResult test14() {
String[] texts = {textToSave2, textToSave1};
String[] files = {filename1, filename2};
for (int i = 0; i < 2; i++) {
String text = texts[i];
String file = files[i];
filenameField.setText("");
textArea.setText("");
filenameField.setText(file);
textArea.setText(text);
menuSave.click();
filenameField.setText("");
textArea.setText("");
filenameField.setText(file);
menuLoad.click();
textArea.requireText(text);
}
return correct();
}
@DynamicTest(feedback = "TextArea should be empty if user tries to " +
"load file that doesn't exist using MenuLoad")
CheckResult test15() {
textArea.setText(textToSave1);
filenameField.setText(noExistFile);
menuLoad.click();
textArea.requireText("");
return correct();
}
@DynamicTest(feedback = "TextArea should correctly save and load an empty file using menu")
CheckResult test16() {
textArea.setText("");
filenameField.setText(filename1);
menuSave.click();
textArea.setText(textToSave2);
menuLoad.click();
textArea.requireText("");
return correct();
}
@After
public void deleteFiles() {
try {
Files.delete(Paths.get(filename1));
Files.delete(Paths.get(filename2));
}
catch (IOException ex) {
ex.printStackTrace();
}
}
}
- Home
- About
- Initial setup
- Writing tests
- Guidelines for writing tests
- Outcomes of testing
- Generating and checking
- Presentation error
- Checking JSON
- Testing solutions written in different languages
- Creating Hyperskill problems based on hs-test
- Testing Java Swing applications
- Testing Java Spring applications
- Testing Ktor applications