如何使用JUnit对JavaFX控制器进行unit testing

什么是初始化JavaFX运行时的正确方法,以便您可以使用并发工具和Platform.runLater(Runnable)进行unit testing(使用JUnit)控制器?

@BeforeClass方法调用Application.launch(...)导致死锁。 如果未调用Application.launch(...)则抛出以下错误:

 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) 

跟进:这是我根据@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... 

@BeforeClass调用launch()是一种正确的方法。 请注意, launch()不会将控制权返回给调用代码。 所以你必须将它包装到new Thread(...).start()

我发现这个工作,…但只有在启动JavaFX应用程序线程后添加Thread.sleep(500)。 据推测,需要一些时间才能完成FX环境并准备好(我的MacBook Pro视网膜上约200ms)

 @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); }