Testing multiple JavaFX components using JemmyFX
Asked Answered
H

2

6

I am working on a fairly large project which includes a set of custom JavaFX components. For each custom component which is meant to be reused, I have started to write a set of automated tests using JemmyFX and JUnit. During development, I run these from inside Eclipse Juno.

Running the entire suite of tests at once has proven difficult. The problem seems to stem from the fact since I want to test multiple components, I would ideally run a different application for each (so that tests performed on one component do not affect other tests).

I created a base class which does the following:

@BeforeClass
public static void beforeClass() {

    Thread t = new Thread("JavaFX Init Thread") {

        @Override
        public void run() {
            Application.launch(UITester.class, new String[0]);
        }
    };

    t.setDaemon(true);
    t.start();
}

Using this base class, I created a separate class with @Test tests for each custom control. When I run this test suite, the first test case runs fine, but the rest fail:

Exception in thread "JavaFX Init Thread" java.lang.IllegalStateException: Application launch must not be called more than once

I have tried the following ways to address this problem:

Shut down the application

I added the following to the base class:

@AfterClass
public static void afterClass() {
    Platform.exit();
}

Same problem persists. Perhaps because the VM is not restarted between tests?

Put in a guard against restarting the application

I put in a static variable to check and see if perhaps the application is already running. This makes the problem go away when I run the tests from Eclipse. When I run them from the command line, the problem is still there. Not good for when we try to run these tests on the integration server.

Catch the IllegalStateException

This is an odd one. I can catch the exception, and most of my problems go away, except for the fact that every 4 or 5 runs of the entire test suite Ubuntu crashes to the shell and I have to log back in.

So, how do I best write tests for a large suite of custom controls? Is my approach incorrect?

Heterodyne answered 20/9, 2013 at 3:55 Comment(4)
Not on my PC now but I think JemmyFx has a special class (AppExecutor?) to run the Application - that might solve your issue. Alternatively you could run each test class in a new VM (junit probably has a parameter to do that) but that will be show.Foeman
@Foeman Yeah, AppExecutor.executeNoBlock(UITester.class); As far as I understand it, it does the same thing as my original sample (and was the call I originally made to start the tests, with the same effect).Heterodyne
I just stumbled across MarvinFX which has a promising way of doing what I am trying to do.Heterodyne
I have the same problem. Awaiting for a better solution, i put only one test method per test class. On the CI, each test is run in a separate jvm. For eclipse, i use the same solution (static flag).Mendel
H
3

After looking at the source of MarvinFX I was able to implement our test framework in a fashion that addresses my problems. What appears to have contributed most to fix this problem was rebuilding the stage and scene for every test, as demonstrated in this (pseudo) code:

@Before
public void before() {
    Node node = generateComponentToTest();
    Parent parent = StackPaneBuilder.create().children(node).build();
    Scene scene = SceneBuilder.create().root(parent).build();

    if (this.currentStage != null) {
        this.currentStage.close();
    }

    Stage stage = new Stage();
    stage.setScene(scene);
    stage.centerOnScreen();
    stage.show();

    this.currentStage = stage;
}
Heterodyne answered 30/9, 2013 at 20:36 Comment(0)
P
0

I have fixed this issue by checking the current threads - if "JavaFX Init Thread" is one of them, just return from the initJavaFX().

    public synchronized void initJavaFX() throws InterruptedException {
        if (isThreadAlreadyRunning()) {
            return;
        }

        Thread t = new Thread(JAVA_FX_INIT_THREAD_NAME) {
            @Override
            public void run() {
                Application.launch(DummyFXApp.class);
            }
        };
        t.setDaemon(true);
        t.start();

    }

    private static boolean isThreadAlreadyRunning() {
        return Thread.getAllStackTraces().keySet().stream()
            .map(Thread::getName)
            .anyMatch(it -> it.equals(JAVA_FX_INIT_THREAD_NAME));
    }
Pardoes answered 19/1 at 13:4 Comment(1)
what is the first line of the code? public synchronized void initJavaFX() throws InterruptedException { i do not think it will work.Precarious

© 2022 - 2024 — McMap. All rights reserved.