User Interface Testing with TestFX

TestFX

robot.clickOn("#buttonNew");
verifyThat("#mainWordPane", isVisible());
verifyThat("#mainWord", hasText("and"));

VocabHunter

The GUI Walkthrough

./gradlew :gui:test --tests io.github.vocabhunter.gui.main.GuiTest \
--rerun-tasks -PnoHeadless
  • Perform an action, for example click a button to classify a word.
  • Validate the new state, for example check that a new word is shown on the main display, awaiting classification.

Automated GUI Testing

private void step(final String step, final Runnable runnable) {
++stepNo;
LOG.info("STEP {}: Begin - {}", stepNo, step);
runnable.run();
LOG.info("STEP {}: End - {}", stepNo, step);
}
step("Start new session", () -> {
robot.clickOn("#buttonNew");
verifyThat("#mainWordPane", isVisible());
verifyThat("#mainWord", hasText("and"));
});

Using TestFX

robot.clickOn("#buttonNew");
verifyThat("#mainWordPane", isVisible());
verifyThat("#mainWord", hasText("and"));
@BeforeAll
public static void setupSpec() throws Exception {
// Set up JavaFX system
// ....

registerPrimaryStage();
}
@BeforeEach
public void setUp() throws Exception {
// Prepare the application for testing
// ...

setupApplication(VocabHunterGuiExecutable.class);
}
<Label fx:id="mainWord" styleClass="mainWord"
text="Main Word" />
<Label fx:id="useCountLabel" styleClass="useCount"
text="(110 uses)" />
verifyThat("#mainWord", hasText("and"));

Problems and Solutions

@BeforeEach
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
@Mock
private WebPageTool webPageTool;
  • The test then behaves differently on different machines, depending of the screen resolution of the test machine.
  • When running in “headless mode” with no display at all, the screen size might not be what you expect.

Dependency Injection

  • CoreGuiModule - This defines the components that are shared by both the “real” VocabHunter and the version that is built for the GUI test.
  • LiveGuiModule - This is where the remaining components for the “real” VocabHunter are defined. These are the components that aren’t used in the test. Instead, mocks are injected into the GUI test, as described in the previous section. This module is therefore excluded in the GUI test and instead a test module is constructed containing the mocks.
if (!project.hasProperty("noHeadless")) {
jvmArgs "-Dheadless=true"
}
if (Boolean.getBoolean("headless")) {
System.setProperty("testfx.robot", "glass");
System.setProperty("testfx.headless", "true");
System.setProperty("prism.order", "sw");
System.setProperty("prism.text", "t2k");
System.setProperty("java.awt.headless", "true");
}

Gradle Dependencies

testCompile 'org.testfx:testfx-core:4.0.12-alpha'
testRuntime 'org.testfx:openjfx-monocle:8u76-b04'
testCompile('org.testfx:testfx-core:4.0.12-alpha') {
exclude group: 'org.testfx',
module: 'testfx-internal-java8'
}
testRuntime 'org.testfx:testfx-internal-java9:4.0.12-alpha'
testRuntime 'org.testfx:openjfx-monocle:jdk-9+181'
if (JavaVersion.current() == JavaVersion.VERSION_1_9) {
testCompile('org.testfx:testfx-core:4.0.12-alpha') {
exclude group: 'org.testfx',
module: 'testfx-internal-java8'
}
testRuntime 'org.testfx:testfx-internal-java9:4.0.12-alpha'
testRuntime 'org.testfx:openjfx-monocle:jdk-9+181'
} else {
testCompile 'org.testfx:testfx-core:4.0.12-alpha'
testRuntime 'org.testfx:openjfx-monocle:8u76-b04'
}

Some General Advice

Final Words

--

--

I’m a Java software developer, based in Barcelona

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Adam Carroll

Adam Carroll

I’m a Java software developer, based in Barcelona