How to unit test that a JavaFX application launches
Asked Answered
A

2

6

I've got a JavaFX application, and I want to test if it launches or not. How would I go about doing that? Is it possible with just JUnit, or can TestFX help me in that?

My main issue is: How do I shut down the application right after it has (succesfully) launched?

Example application class:

public class MovieDB extends Application {
    @Override
    public void start(final Stage primaryStage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(MovieDBController.class.getResource("MovieDB.fxml"), ResourceBundle.getBundle("bundles/bundle", new Locale("en")));
        Parent root = fxmlLoader.load();

        Scene scene = new Scene(root, 1024, 768);

        StyleManager.getInstance().addUserAgentStylesheet(getClass().getResource("/css/MovieDB.css").getPath());

        primaryStage.setTitle("MovieDB");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
Aut answered 20/7, 2014 at 15:16 Comment(0)
M
3

Since the Application.launch method does not return until the application has exited, either via a call to Platform.exit or all of the application windows have been closed so you have to wrap it into another thread in order to terminate it.

If you call Platform.exit right after the JavaFX application launches you will get IllegalStateException. If you wait for a while so your JavaFX application can be initialized and then call Platform.exit, both your JavaFX application and your wrapper thread will be terminated without completing or throwing any exception. I couldn't find a way to work that out by using Platform.exit.

However, I managed to do it by using Thread.interrupt. Simply run your JavaFX application inside a wrapper thread, wait for a while and then interrupt your wrapper thread. This way the JavaFX application will be interrupted and throw InterruptedException. If it does not throw then there is an issue launching your JavaFX application.

Note that it may take longer than you wait for JVM to launch JavaFX application so this method does not guarantee that the JavaFX application is interrupted after it is properly launched which might result in a false negative situation.

Test Class

import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import static org.junit.Assert.assertTrue;
import org.junit.Test;

public class JavaFXTest {

    // Wrapper thread updates this if
    // the JavaFX application runs without a problem.
    // Declared volatile to ensure that writes are visible to every thread.
    private volatile boolean success = false;

    /**
     * Test that a JavaFX application launches.
     */
    @Test
    public void testMain() {
        Thread thread = new Thread() { // Wrapper thread.
            @Override
            public void run() {
                try {
                    Application.launch(JavaFXTest.class); // Run JavaFX application.
                    success = true;
                } catch(Throwable t) {
                    if(t.getCause() != null && t.getCause().getClass().equals(InterruptedException.class)) {
                        // We expect to get this exception since we interrupted
                        // the JavaFX application.
                        success = true;
                        return;
                    }
                    // This is not the exception we are looking for so log it.
                    Logger.getLogger(JavaFXTest.class.getName()).log(Level.SEVERE, null, t);
                }
            }
        };
        thread.setDaemon(true);
        thread.start();
        try {
            Thread.sleep(3000);  // Wait for 3 seconds before interrupting JavaFX application
        } catch(InterruptedException ex) {
            // We don't care if we wake up early.
        }
        thread.interrupt();
        try {
            thread.join(1); // Wait 1 second for our wrapper thread to finish.
        } catch(InterruptedException ex) {
            // We don't care if we wake up early.
        }
        assertTrue(success);
    }
}

JavaFX Application Class

import java.io.IOException;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class JavaFX extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws IOException {
        primaryStage.setTitle("JavaFX");
        Label label = new Label("Hello World!");
        StackPane root = new StackPane();
        root.getChildren().add(label);
        primaryStage.setScene(new Scene(root, 250, 250));
        primaryStage.show();
    }
}
Miltiades answered 7/2, 2017 at 20:24 Comment(1)
Application.launch signature is not Object or ?. It is Application.launch(<? extends Application> appClass). This will cause runtime error which says RuntimeException: Error: 'YourApplication' is not a subclass of javafx.application.Application.Dukes
I
0

Assuming that primaryStage is the only open stage, the JavaFX thread will automatically shut down when you call primaryStage.hide(). This is because JavaFX is set by default to shutdown when all stages are hidden, which can be changed by calling Platform.setImplicitExit(false).

More info here.

Imperfection answered 5/8, 2014 at 19:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.