I wrote a rather complex JavaFx 2 application for which I'd like to write a bunch of unit tests. Problem is when I try to conduct the tests I get a runtime error complaining about uninitialized toolkit.
From what I can tell I should somehow invoke Application.launch() in a @BeforeClass method but this causes a deadlock as Application.launch() doesn't return to calling thread.
So question is how should I initialize JavaFx?
This is the skeleton of the code that doesn't work:
public class AppTest extends Application {
@BeforeClass
public void initialize() {
launch(); //this causes a deadlock
}
@Test
public void test1() {
//conduct test here
}
@Test
public void test2() {
//conduct other test here
}
@Override
public void start(Stage arg0) throws Exception {
}
Thanks in advance!
From another question here on stackoverflow, I've made myself this little helper class:
import javafx.application.Application;
import javafx.stage.Stage;
public class JavaFXInitializer extends Application {
private static Object barrier = new Object();
@Override
public void start(Stage primaryStage) throws Exception {
synchronized(barrier) {
barrier.notify();
}
}
public static void initialize() throws InterruptedException {
Thread t = new Thread("JavaFX Init Thread") {
public void run() {
Application.launch(JavaFXInitializer.class, new String[0]);
}
};
t.setDaemon(true);
t.start();
synchronized(barrier) {
barrier.wait();
}
}
}
which can then be used easily in a @BeforeClass setup method:
@BeforeClass
public void setup() throws InterruptedException {
JavaFXInitializer.initialize();
}
The main think is to consider your tests to be run inside an FX thread. When you create a class extends Application, you create in fact a process. This is what you want to test.
So to launch some unit tests on an Application, first create an FXAppTest that extends Application and then inside FXAppTest you launch your unit test. Here is the idea.
Here is an example with JUnit. I create a Runner that launch the test inside an FXApp for test.
Here is an example of code for FxApplicationTest (we launch unit test inside it)
public class FxApplicationTest extends Application {
private volatile boolean isStopped;
@Override
public void start(final Stage stage) {
StackPane root = new StackPane();
Scene scene = new Scene(root, 10, 10);
stage.setScene(scene);
}
public void startApp() {
launch();
}
public void execute(final BlockJUnit4ClassRunner runner, final RunNotifier notifier) throws InterruptedException {
isStopped = false;
Platform.runLater(new Runnable() {
@Override
public void run() {
runner.run(notifier);
isStopped = true;
}
});
while (!isStopped) {
Thread.sleep(100);
}
}
And the Runner :
import org.apache.log4j.Logger;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.InitializationError;
public class JUnitFxRunner extends Runner {
private final BlockJUnit4ClassRunner runner;
private final Logger LOGGER = Logger.getLogger(JUnitFxRunner.class);
public JUnitFxRunner(final Class<?> klass) throws InitializationError {
super();
runner = new BlockJUnit4ClassRunner(klass);
}
@Override
public Description getDescription() {
return Description.EMPTY;
}
@Override
public void run(final RunNotifier notifier) {
try {
final FxApplicationTest fxApplicationTest = new FxApplicationTest();
MyTestRunner runnable = new MyTestRunner(runner, notifier, fxApplicationTest);
new Thread(runnable).start();
Thread.sleep(100);
runnable.execute();
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
private class MyTestRunner implements Runnable {
private final BlockJUnit4ClassRunner runner;
private final RunNotifier notifier;
private final FxApplicationTest fxApp;
public MyTestRunner(final BlockJUnit4ClassRunner runner, final RunNotifier notifier, final FxApplicationTest fxApp) {
this.runner = runner;
this.notifier = notifier;
this.fxApp = fxApp;
}
@Override
public void run() {
fxApp.startApp();
}
public void execute() throws InterruptedException {
fxApp.execute(runner, notifier);
}
}
}
Now, simply launch test using the runner :
import fr.samarie_projects.fx.utils.JUnitFxRunner;
@RunWith(JUnitFxRunner.class)
public class MainFxAppTest {
@org.junit.Test
public void testName() throws Exception {
MainFxApp fxApp = new MainFxApp();
fxApp.start(new Stage());
}
}
This unit test MainFxApp
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class MainFxApp extends Application {
@Override
public void start(final Stage stage) throws Exception {
StackPane root = new StackPane();
Scene scene = new Scene(root, 10, 10);
stage.setScene(scene);
}
public static void main(final String[] args) {
launch(args);
}
}
Sure, this code need to be reviewed. It is only to present the idea.
Well, considering that you might have your JavaFX app located at project-root/src/main/java/package/FXApp.java then you might have your tests located elsewhere like project-root/src/test/java/package/FXAppTest.java . This being the case, the FXAppTest class could call the FXApp class by initializing it using BeforeClass .
In theory you should be able to start your FX app with something like:
// imports located here that import junit 4.11+ packages (or TestNG)
public class FXAppTest {
@BeforeClass
public void initialize() {
FXApp fxa = new FXApp();
while ( fxa.isLoading() ) {
// do nothing
}
}
....
NOTE: Notice that FXAppTest does not extend Application here.
Now, if this doesn't clue you into the problem, you could enable JMX args on the JVM and then view the locked threads with JVisualVM.