What's the proper way of initializing the JavaFX runtime so you can unit test (with JUnit) controllers that make use of the concurrency facilities and Platform.runLater(Runnable)
?
Calling Application.launch(...)
from the @BeforeClass
method results in a dead lock. If Application.launch(...)
is not called then the following error is thrown:
java.lang.IllegalStateException: Toolkit not initialized at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:121) at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:116) at javafx.application.Platform.runLater(Platform.java:52) at javafx.concurrent.Task.runLater(Task.java:1042) at javafx.concurrent.Task.updateMessage(Task.java:987) at com.xyz.AudioSegmentExtractor.call(AudioSegmentExtractor.java:64) at com.xyz.CompletionControllerTest.setUp(CompletionControllerTest.java:69) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184) at org.junit.runners.ParentRunner.run(ParentRunner.java:236) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Followup: this is the motif I've been using based on recommendation by @SergeyGrinev.
... // Inside test class public static class AsNonApp extends Application { @Override public void start(Stage primaryStage) throws Exception { // noop } } @BeforeClass public static void initJFX() { Thread t = new Thread("JavaFX Init Thread") { public void run() { Application.launch(AsNonApp.class, new String[0]); } }; t.setDaemon(true); t.start(); } ... // controller tests follow...
Writing a Unit Test for REST Controller First, we need to create Abstract class file used to create web application context by using MockMvc and define the mapToJson() and mapFromJson() methods to convert the Java object into JSON string and convert the JSON string into Java object.
JUnit can be used as a test runner for any kind of test: e.g. system and integration tests; tests which are interacting with a deployed application.
Calling launch()
from @BeforeClass
is a correct approach. Just note that launch()
doesn't return control to calling code. So you have to wrap it into new Thread(...).start()
.
A 7 years later update:
Use TestFX! It will take care of launching in a proper way. E.g. you can extend your test from a TestFX's ApplicaionTest class and just use the same code:
public class MyTest extends ApplicationTest { @Override public void start (Stage stage) throws Exception { FXMLLoader loader = new FXMLLoader( getClass().getResource("mypage.fxml")); stage.setScene(scene = new Scene(loader.load(), 300, 300)); stage.show(); }
and write tests like that:
@Test public void testBlueHasOnlyOneEntry() { clickOn("#tfSearch").write("blue"); verifyThat("#labelCount", hasText("1")); }
I found this to work,... but only after adding a Thread.sleep(500) after starting the JavaFX application thread. Presumably it takes some time to get the FX environment up and ready (about 200ms on my MacBook Pro retina)
@BeforeClass public static void setUpClass() throws InterruptedException { // Initialise Java FX System.out.printf("About to launch FX App\n"); Thread t = new Thread("JavaFX Init Thread") { public void run() { Application.launch(AsNonApp.class, new String[0]); } }; t.setDaemon(true); t.start(); System.out.printf("FX App thread started\n"); Thread.sleep(500); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With