TestFX internals explained

TestFX a small but powerful framework for JavaFX GUI Testing with jUnit.
How it is used and how it is working inside?

TDD for GUI development is for a lot of developers something that make them feeling uncomfortable.
Using jUnit for the testing of the GUI Elements is mostly not what you want to do.
But with TestFX it is quite simple to write short but reusable tests.

Let´s start with the project initialization. You need only a few entries in you pom.xml.
jUnit, hamcrest and testfx, that is it.. and nothing more.


    org.loadui
    testFx
    3.0.0 


    org.hamcrest
    hamcrest-all
    1.3


    junit
    junit
    4.11
    
        
            org.hamcrest
            hamcrest-core
        
    

After we added this entries to the pom.xml, we can start with the first jUnitTest using TestFX.
We will start with the traditional HelloWorld. The first step is the creation of a TestClass.
The TestClass must extend from GuiTest, a base class from the TestFX Framework.
This class is abstract and you have to implement the method protected Parent getRootNode()
This method must create the GUI element that you will test. I our example we want to test a simple button.

@Category(TestFX.class)
public class SimpleButtonTest extends GuiTest {
    @Override
    protected Parent getRootNode() {
        final Button btn = new Button();
        btn.setId("btn");
        btn.setText("Hello World");
        btn.setOnAction((actionEvent)-> btn.setText( "was clicked" ));
        return btn;
    }
}
You can see, that you don´t have to create a holder for your GUI element.
Now we can start with the first test itself.
The button will change the text from "Hello World" to "was clicked" if the user will click the button.
How we can test this?
As normal we have to write a test method annotated with the @Test from jUnit.
The first question will be, how to get the reference of the button?
For this you can use one of the service methods. With find and the id you will get the instance.

@Test
public void shouldClickButton(){
    final Button button = find( "#btn" );
    click(button);
    verifyThat( "#btn", hasText("was clicked") );
}
As you can see, we can start with the functional tests after we got the reference. Here we are simulating the click.
After this we are checking if the result is as expected.
Writing tests looks now like , writing the use case with methods organized with the builder pattern.
If you want to write your own check like hasText() you have to write it with the framework hamcrest.
But TestFX will provide a lot of default checks and test-steps. Mostly you don not need more.

But how TestFX is doing all this?
Starting with the test class GUITest, we know that we have to create the GUI element we want to test.
The class GUITest itself contains a lot of methods to describe the use case with
actions like click,drag,move,push, scroll, rightClick and so on.
But how the application is started? This will be done by the internal class TestFXApp inside GUITest.

public static class TestFxApp extends Application
{
    private static Scene scene = null;

    @Override
    public void start( Stage primaryStage ) throws Exception
    {
        primaryStage.initStyle(StageStyle.UNDECORATED);
        primaryStage.show();
        stageFuture.set( primaryStage );
    }

    public static void setRoot( Parent rootNode )
    {
        scene.setRoot( rootNode );
    }
}
Every developer that was writing the first few jUnit tests for an JavaFX application
came to the point where the JVM was telling something like
"please, only one instance of the JavaFX Application please"
How to deal with this?
The basic is quite simple. You have to start the application in one thread, save this as an singleton over all jUnit tests in one JVM.
After this, put every junit test method in one Callable and start them one after an other.
The first step will be done with the method setupStage, annotated with @Before.
Inside this there is a check if the singleton is already there or not. If not, it will be created. FXTestUtils.launchApp(TestFxApp.class);
After this the GUI element from getRootNode() will be placed in a new Runnable.

@Before
public void setupStage() throws Throwable{
    showNodeInStage();
}

private void showNodeInStage(){
    showNodeInStage(null);
}

private void showNodeInStage( final String stylesheet ){
    GuiTest.stylesheet = stylesheet;

    if( stage == null ){
        FXTestUtils.launchApp(TestFxApp.class);
        try{
            stage = targetWindow(stageFuture.get( 25,TimeUnit.SECONDS ) );
            FXTestUtils.bringToFront( stage );
        }catch( Exception e ) {
            throw new RuntimeException( "Unable to show stage", e );
        }
    }

    try{
        FXTestUtils.invokeAndWait( new Runnable(){
            @Override
            public void run(){
                Scene scene = SceneBuilder
                    .create()width( 600 ) height( 400 )
                    .root( getRootNode() ).build();

                if(stylesheet!=null) 
                    scene.getStylesheets().add(stylesheet);
                stage.setScene( scene );
            }
        }, 5 );
    }catch( Exception e ) {
        e.printStackTrace();
    }
}

That´s it. Quite simple and easy to use for a developer.
The good thing is, that TestFX is good usable in an CI environment.
You only have to configure jUnit if not already done.

But this is not the end.. I am working in the moment at the full CDI support inside
TestFX. We will see it in one of the next versions from TestFX. stay tuned...

If you have any ideas what we can improve.. let me know..

happy javafx testing

Kommentare

Beliebte Posts