Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you unit test a JavaFX controller with JUnit

Tags:

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... 
like image 752
metasim Avatar asked Jul 08 '12 18:07

metasim


People also ask

How do you write a unit test for a controller?

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.

Can JUnit be used for system testing?

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.


2 Answers

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")); } 
like image 114
Sergey Grinev Avatar answered Oct 04 '22 00:10

Sergey Grinev


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); } 
like image 22
Michael Ellis Avatar answered Oct 04 '22 00:10

Michael Ellis