Describes how to test JavaFX code using TestFX, a test library for JavaFX.
A library for implementing tests in JavaFX. You can use this library to implement UI tests as well as unit tests. The license is EUPL v1.1.
This time I will try using 4 series. As of January 2017, when this article was written, 4.0.5-alpha
is the latest.
When I implement and run a test that includes a JavaFX application or JavaFX class using regular JUnit, I get `` `java.lang.ExceptionInInitializerError``` as shown below.
StackTrace
java.lang.ExceptionInInitializerError
at sun.reflect.GeneratedSerializationConstructorAccessor2.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at org.objenesis.instantiator.sun.SunReflectionFactoryInstantiator.newInstance(SunReflectionFactoryInstantiator.java:40)
at org.objenesis.ObjenesisBase.newInstance(ObjenesisBase.java:59)
at org.mockito.internal.creation.jmock.ClassImposterizer.createProxy(ClassImposterizer.java:128)
... Omitted ...
at org.mockito.Mockito.mock(Mockito.java:1120)
at jp.toastkid.loto6.Loto6ControllerTest.setUpTarget(Loto6ControllerTest.java:38)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
... Omitted ...
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: java.lang.IllegalStateException: Toolkit not initialized
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:273)
... Omitted ...
at javafx.scene.control.Control.<clinit>(Control.java:87)
... 37 more
Caused by: java.lang.IllegalStateException:Toolkit not initialized.[How do you unit test a JavaFX controller with JUnit](http://stackoverflow.com/questions/11385604/how-do-you-unit-test-a-javafx-controller-with-junit)It seems that it can be implemented by using the method described in, but it is a bit complicated ... So I decided to use the existing library.
## Environment and others
This implementation and confirmation was carried out in the following environment.
| Java SE | 1.8.0_102
|:---|:---
| OS | Windows 10
| Eclipse | 4.5
| JFoenix | 1.0.0
| JUnit | 4.11
| Mockito | 1.9.5
| TestFX | 4.0.5-alpha
----
# Introducing the library
For Gradle, all you need to do is add a line of testCompile dependencies to your dependencies.
#### **`build.gradle fix`**
```groovy
testCompile group: 'org.testfx', name: 'testfx-junit', version: '4.0.5-alpha'
Alternatively, this format is fine.
build.gradle fix
testCompile 'org.testfx:testfx-junit:4.0.5-alpha'
If you are using a build tool other than Gradle, please check mvnrepository.com.
Guava etc. are included as dependencies.
dependencies
org.testfx:testfx-junit:4.0.5-alpha
\--- org.testfx:testfx-core:4.0.5-alpha
+--- com.google.guava:guava:20.0
+--- org.hamcrest:hamcrest-core:1.3
\--- com.google.code.findbugs:annotations:3.0.1u2
+--- net.jcip:jcip-annotations:1.0
\--- com.google.code.findbugs:jsr305:3.0.1
The test class inherits from the org.testfx.framework.junit.ApplicationTest
class. It seems that up to 3 series was named `GuiTest```. In the 4th series, it has been renamed to ApplicationTest. This class must implement the
`public void start (Stage stage) throws Exception``` method.
In TestFX, the main style is to write tests for the Controller class.
This time, I will implement the test in the JavaFX application that runs scripts that I created earlier.
I have a screen like this. The code of Groovy entered by the user is changed to ScriptEngine. api / javax / script / ScriptEngine.html), and when you click the Button that says run, it has the function of displaying the execution result of the script. The left side of the screen is the CodeArea (TextArea) that accepts code input from the user, and the left side is the CodeArea (TextArea) that displays the execution result.
Initialize and display the GUI in this method. It is executed before the method with @Before. This time, I will describe the initialization process before displaying the screen according to the implementation of the class that calls Controller.
Test class
@Override
public void start(final Stage stage) throws Exception {
try {
final FXMLLoader loader
= new FXMLLoader(getClass().getClassLoader().getResource("scenes/Main.fxml"));
final VBox loaded = (VBox) loader.load();
controller = (Controller) loader.getController();
final Scene scene = new Scene(loaded);
stage.setScene(scene);
} catch (final IOException e) {
LOGGER.error("Scene Reading Error", e);
}
stage.show();
}
As mentioned above, we are loading FXML, getting the Controller, initializing the Scene and Stage, and finally displaying the stage.
We also have an empty test method to run JUnit's Runner.
Empty test method
@Test
public void test() {
// NOP.
}
When you run the JUnit test, you will see the application screen as shown below. Since no other test method has been implemented yet, it will only appear for a moment and then disappear immediately.
I will briefly explain the application for which I will write tests this time. It has a function to execute the Groovy code entered by the user in ScriptEngine and click the Button labeled run to display the execution result of the script. The left side of the screen is the CodeArea (TextArea) that accepts code input from the user, and the left side is the CodeArea (TextArea) that displays the execution result.
Next, let's add the code for the UI test.
Try entering Groovy's Hello World.
Enter the script in the Text Area on the left
@Test
public void test() throws InterruptedException {
final CodeArea input = (CodeArea) lookup("#scripterInput").query();
final String text = "println 'Hello world.'";
Platform.runLater(() -> input.replaceText(text));
}
Nodes included in the displayed Scene can be looked up by ID. The ID can be defined in FXML or specified by setId in the Node class. An example definition in FXML is shown below.
Definition example in FXML
<CodeArea fx:id="scripterInput" prefHeight="450.0" prefWidth="500.0">
scripterinput
Look up the node with the id named codearea, cast it to the implementation class codearea, and use it in subsequent tests.
Lookup->cast
final CodeArea input = (CodeArea) lookup("#scripterInput").query();
On JUnit test classes, setting JavaFX Control to a value will fail if you don't use Platform.runLater
.
Platform.runLater(() -> input.replaceText(text));
When you run the test, the screen will be displayed with the script entered as shown below.
The code to get the Button that says run with Lookup and execute the set event can be written in one line below.
Lookup->query->fireEvent
Platform.runLater(() -> lookup("#runButton").query().fireEvent(new ActionEvent()));
Since the run Button is specified in FXML to execute a method called runScript of the Controller class, that method is executed, and the CodeArea (TextArea) on the right side of the screen is the result of executing the script `Hello world.`
Is displayed.
All you have to do is check the value with the assert method.
Final test code
@Test
public void test() throws InterruptedException {
final CodeArea input = (CodeArea) lookup("#scripterInput").query();
final String text = "println 'Hello world.'";
Platform.runLater(() -> {
input.replaceText(text);
assertEquals(text, input.getText());
lookup("#runButton").query().fireEvent(new ActionEvent());
assertEquals(
"Hello world.",
((CodeArea) lookup("#scripterOutput").query()).getText().trim()
);
});
}
Method execution is asynchronous inside and outside Platform.runLater, so if you want to check the value of Node caused by a change to Node made by Platform.runLater, you need to write it in Platform.runLater.
As an example of failure, the code below always fails because the check with assertEquals is performed before ```input.replaceText (text) `` `in Platform.runLater.
Case where inspection fails
Platform.runLater(() -> input.replaceText(text));
assertEquals(text, input.getText());
You can also write the same code using Mockito's Whitebox without using the TestFX Lookup (Note: the Controller must have its own Node object).
@Test
public void test_mockito() throws InterruptedException {
final CodeArea input = (CodeArea) Whitebox.getInternalState(controller, "scripterInput");
final String text = "println 'Hello world.'";
Platform.runLater(() -> {
input.replaceText(text);
assertEquals(text, input.getText());
((Button) Whitebox.getInternalState(controller, "runButton")).fireEvent(new ActionEvent());
assertEquals(
"Hello world.",
((CodeArea) Whitebox.getInternalState(controller, "scripterOutput")).getText().trim()
);
});
}
The advantage of using Mockito is that you can run tests without displaying the screen. This method is recommended for those who are annoyed by the screen being displayed every time.
So far, I have briefly explained how to write tests for JavaFX applications using TestFX. You might think it's annoying to see the screen every time you run a test. It's annoying. If you want to test without displaying the screen, it is better to use Mock to build the test. Even then, TestFX makes it easier to write tests because you don't have to deal with the thread-related parts of JavaFX yourself. I didn't introduce it this time, but if you want to write unit tests for your own JavaFX components, you can use this TestFX.
There is already a lot of information about TestFX in English and Japanese.
-TestFX Official Documentation
There is a lot of information on the 3rd system.
-First step to start JavaFX automatic test tool TestFX -Test JavaFX UI in JUnit format -Testing JavaFX application with TestFX
-This correction difference (GitHub) -Overall code of JavaFX application implementing test (GitHub)
Recommended Posts