Swing2JavaFX Live Coding Example (WJAX)

Last week I did some Swing2JavaFX Live Coding on the WJAX in Munich (Details). Therefore I migrated a random Swing client from GitHub partially to JavaFX. I created a fork and a screencast (German), which gives you an impression, which technical opportunities you have, if you migrate Swing to JavaFX.

I also pointed some hurdles and showed workarounds for existing problems if you migrate from Swing to JavaFX.

GitHub – Fork of the Swing client
https://github.com/sialcasa/Swing2JavaFXHacking

Screencast Swing2JavaFX Live Coding

 

How to test JavaFX Services

In this post we want to take a look on how to write an integration test for a JavaFX Service.  Our showcase is a Service that performs a login and signals whether the login was successful or not. Please have a look on the code below to understand how the login works. As I said, this is an integration test for the combination of the Service and the Task in it. In addition you should write an Unit Test for the task in the Service .

public class LoginService extends Service {

    private final String password;
    private final String username;

    private ClientApi clientApi;

    @Inject
    public LoginService(Client Api, String username, String password) {
        this.username = username;
        this.password = password;
        this.clientApi = clientApi;
    }

    @Override
    protected Task createTask() {
        Task task = new Task() {
            @Override
            protected Boolean call() throws Exception {
                 // true/false for login state, Exception for Networkproblems
                 Boolean login;
                 try {
                      login = clientApi.login(username, password);
                 } catch (Exception e) {
                      //If error occures, set fail message and throw e to make the task fail
                      updateMessage("Error while logging in");
                      throw e;
                 }
                 if (!login) {
                      updateMessage("Invalid credentials");
                 }
                 return login;
            }
        };

        return task;
    }
}

As you can see, the service follows the Dependency Injection pattern, so we can mock the dependencies easily.


What do we want to test?

 

  • Successful login
    • stateProperty() is Worker.State.SUCCEEDED
    • valueProperty() is true
    • messageProperty() is “”

 

  • Failed login because of wrong credentials
    • stateProperty() is Worker.State.SUCCEEDED
    • valueProperty() is true
    • messageProperty() is “Invalid Credentials”

 

  • Failed login because of  network errors
    • stateProperty() is Worker.State.FAILED
    • valueProperty() is false
    • messageProperty() is “Error while logging in” 

 


Problems which occurs when you test a Service

  1. A service needs an initialized JavaFX Toolkit
  2. A services executes the tasks in separate thread which makes it hard to perform asserts

 

Solution for Problem 1

As I already mentioned we need to initialize the JavaFX Toolkit, otherwise we will get this Exception when we start the Service:

java.lang.IllegalStateException: Toolkit not initialized
	at com.sun.javafx.application.PlatformImpl.runLater(Unknown Source)
	at com.sun.javafx.application.PlatformImpl.runLater(Unknown Source)
	at javafx.application.Platform.runLater(Unknown Source)
	at javafx.concurrent.Service.runLater(Unknown Source)
	at javafx.concurrent.Service.start(Unknown Source)
	at de.saxsys.javafx.test.example.LoginServiceTest.failedLoginWithNetworkErrors(LoginServiceTest.java:79)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
	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:459)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

I created a small JUnit TestRunner to solve this problem. The Runner manages the JavaFX Toolkit initialization. Please read the following post for more information:

http://blog.buildpath.de/javafx-testrunner/

We’ll see how to use the Runner in our following code boxes.

Solution for Problem 2

To handle the new thread which is spawned by the Service we use a CompletableFuture<T> that blocks the Test-Thread until the complete method of the future was called.

In the example you can see a CompletableFutures that will complete when the state of the Serviceis the state Worker.State.DONE.

If this happens, we transport the current values of the Service with a DTO through the CompletableFuture back to our test thread.

CompletableFuture<ServiceTestResults> serviceStateDoneFuture = new CompletableFuture<>();

service.stateProperty().addListener((observable, oldValue, newValue) -> {
     //We check, whether the new state is the state we are looking for to make our tests
     if (newValue == Worker.State.Done) {
            serviceStateReady.complete(
             new ServiceTestResults(service.getMessage(), service.getState(),service.getValue())
            );
     }
});

service.start();

// This call is blocking the thread until complete() was called and get() gives an result. You can set a timeout 
ServiceTestResults result = serviceStateDoneFuture.get(100, TimeUnit.MILLISECONDS); 

Assert.assertEquals("YEA WE CAN ASSERT",result.message)
// DTO to transport the results of the check
private class ServiceTestResults {
   
  String message;
  Worker.State state;
  Boolean serviceResult;

  ServiceTestResults(String message, State state, Boolean serviceResult) {
      this.message = message;
      this.state = state;
      this.serviceResult = serviceResult;
  }
}

Test the LoginService

Ok, now we can write the tests for our LoginService. With the combination of the Test Runner and CompletableFuture we can test all cases that we wanted. I hope the code explains itself – If you have further questions please comment under the post.

package de.saxsys.javafx.test.example;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import javafx.concurrent.Worker;
import javafx.concurrent.Worker.State;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

import de.saxsys.javafx.test.JfxRunner;

//The JfxRunner ensures, that an JavaFX-Application is present
@RunWith(JfxRunner.class)
public class LoginServiceTest {

    private static final String EMPTY_STRING = "";

    @Test
    public void testStateBySuccessFulLogin() throws Exception {
        // Mock the CientAPI to have a successful login
        ClientApi mockedClientApi = (username, password) -> true;
        // Create the Service with the mocked ClientAPI
        LoginService service = new LoginService(mockedClientApi, EMPTY_STRING, EMPTY_STRING);

        // Create a completableFuture to test the background task of the
        // service
        CompletableFuture<ServiceTestResults> stateCompareableFuture = createStateCompareFuture(service,
                Worker.State.SUCCEEDED);

        // We are not in the UI Thread so we have to start the service in
        // runLater()
        service.start();

        // get the result of the CompareableFuture with a timeout when the
        // result does not appear
        ServiceTestResults result = stateCompareableFuture.get(100, TimeUnit.MILLISECONDS);

        // Tests
        Assert.assertEquals(Worker.State.SUCCEEDED, result.state);
        Assert.assertEquals(EMPTY_STRING, result.message);
        Assert.assertTrue(result.serviceResult);
    }

    @Test
    public void failedLoginWithWrongCredentials() throws Exception {
        // Mock the CientAPI to have a failed login because of invalid
        // credentials
        ClientApi mockedClientApi = (username, password) -> false;
        // Create the Service with the mocked ClientAPI
        LoginService service = new LoginService(mockedClientApi, EMPTY_STRING, EMPTY_STRING);
        // Create a completableFuture to test the background task of the
        // service
        CompletableFuture<ServiceTestResults> stateCompareFuture = createStateCompareFuture(service,
                Worker.State.SUCCEEDED);

        service.start();

        // get the result of the CompareableFuture with a timeout when the
        // result does not appear
        ServiceTestResults result = stateCompareFuture.get(100, TimeUnit.MILLISECONDS);

        // Tests
        Assert.assertEquals(Worker.State.SUCCEEDED, result.state);
        Assert.assertEquals("Invalid credentials", result.message);
        Assert.assertFalse(result.serviceResult);
    }

    @Test
    public void failedLoginWithNetworkErrors() throws Exception {
        // Mock the CientAPI to have a failed login with some errors
        ClientApi mockedClientApi = (username, password) -> {
            throw new RuntimeException();
        };
        // Create the Service with the mocked ClientAPI
        LoginService service = new LoginService(mockedClientApi, EMPTY_STRING, EMPTY_STRING);

        // Create a complteableFuture to test the background task of the
        // service
        CompletableFuture<ServiceTestResults> stateCompareFuture = createStateCompareFuture(service,
                Worker.State.FAILED);

        service.start();

        // get the result of the CompareableFuture with a timeout when the
        // result does not appear
        ServiceTestResults result = stateCompareFuture.get(100, TimeUnit.MILLISECONDS);

        // Tests
        Assert.assertEquals(Worker.State.FAILED, result.state);
        Assert.assertEquals("Error while logging in", result.message);
        Assert.assertNull(result.serviceResult);
    }

    // Helper method which creates a compareableFuture that tells us, when the
    // state of the service reached a state which we expect
    private CompletableFuture<ServiceTestResults> createStateCompareFuture(LoginService service,
            Worker.State stateToReach) {
        CompletableFuture<ServiceTestResults> serviceStateReady = new CompletableFuture<>();
        service.stateProperty().addListener(
                (observable, oldValue, newValue) -> {
                    if (newValue == stateToReach) {
                        serviceStateReady.complete(new ServiceTestResults(service.getMessage(), service.getState(),
                                service.getValue()));
                    }
                });
        return serviceStateReady;
    }

    // DTO to transport the results of the check
    private class ServiceTestResults {
        String message;
        Worker.State state;
        Boolean serviceResult;

        ServiceTestResults(String message, State state, Boolean serviceResult) {
            this.message = message;
            this.state = state;
            this.serviceResult = serviceResult;
        }
    }
}

 

JavaFX Testrunner

 

I deployed a small JUnit TestRunner based on http://awhite.blogspot.de/2013/04/javafx-junit-testing.html into the maven central repository.

Project on GitHub

You can use it by adding the following dependency to your project:

<dependency>
        <groupId>de.saxsys</groupId>
        <artifactId>jfx-testrunner</artifactId>
        <version>1.0</version>
</dependency>

Some JavaFX components does need the JavaFX-Application to be initialized. The TestRunner ensures that you don’t have to mind about an existing JavaFX Application for your Unit Tests. In addition you can choose which test method should be performed in the UI-Thread  by using the Annotation @TestInJfxThread.

 

@RunWith(JfxRunner.class)
public class TestClass {

    @Test
    public void testWithoutFXThread() throws Exception {
        Assert.assertFalse(Platform.isFxApplicationThread());

        CompletableFuture isInApplicationThread = new CompletableFuture<>();
        
        Platform.runLater(() -> {
            isInApplicationThread.complete(Platform.isFxApplicationThread());
        });
        
        Assert.assertTrue(isInApplicationThread.get());
    }

    @Test
    @TestInJfxThread
    public void testWithFXThread() throws Exception {
        Assert.assertTrue(Platform.isFxApplicationThread());
    }
}

JavaFX – Decouple the View and its behavior to create a testable UI

The behavior of an UI is a complex state machine which should be tested. This can be done not only by automated UI-tests, but also with Unit Tests. It’s not easy to write Unit Tests for UIs,  because you need to decouple the state of the UI from its declaration.

One approach is the MVVM pattern. MVVM introduces a ViewModel layer that contains the state and behavior of an UI. The View only represents the ViewModel and reacts on its changes.

Shortfacts MVVM:

  • View does not know the Model
  • Model does not know anybody ;-)
  • ViewModel does not know the View (one difference to MVP)
  • ViewModel knows Model

 

Let’s try it. We want to build a login component which has two text fields (name/password) and a login button. The button should only be active when there is some text in the text fields. You can find the example on GitHub.

Step 1 - Write tests to specify the ViewModel

Because the ViewModel represents the UI state, we create the ViewModel first.Testdriven. Therefore we write an unit test to specify the behavior. The ViewModel should provide three properties which can be accessed

  • userNameProperty() – represents the current user input for the username
  • passwordProperty() - represents the current user input for the password
  • loginPossibleProperty() - represents whether a login is possible based on conditions (username and password not empty)

 

public class LoginViewModelTest {
	@Test
	public void disableLoginButton() throws Exception {
		LoginViewModel loginViewModel = new LoginViewModel();
		// No username and password were set
		Assert.assertFalse(loginViewModel.isLoginPossibleProperty().get());

		loginViewModel.userNameProperty().set("Stefanie Albrecht");
		// username was set, but no password
		Assert.assertFalse(loginViewModel.isLoginPossibleProperty().get());

		loginViewModel.passwordProperty().set("<3");
		// username and password were set
		Assert.assertTrue(loginViewModel.isLoginPossibleProperty().get());
	}
}

Step 2 - Implement the ViewModel, so the tests can pass

Yea – we have a test, but the test won’t compile and pass, if we don’t provide a proper implementation. So lets implement the ViewModel.

The userName and the password properties are representing the current input of upcoming text fields, so they are of the type StringProperty. The loginPossible property signals whether the user provided some input for the username and password field. It should be true, if both other properties have values.

public class LoginViewModel {

	private final StringProperty userName = new SimpleStringProperty();
	private final StringProperty password = new SimpleStringProperty();
	private final ReadOnlyBooleanWrapper loginPossible = new ReadOnlyBooleanWrapper();

	public LoginViewModel() {
                //Logic to check, whether the login is possible or not
		loginPossible.bind(userName.isNotEmpty().and(password.isNotEmpty()));
	}

	public StringProperty userNameProperty() {
		return userName;
	}

	public StringProperty passwordProperty() {
		return password;
	}

	public ReadOnlyBooleanProperty isLoginPossibleProperty() {
		return loginPossible.getReadOnlyProperty();
	}
}

Step 3 - Implement the View and bind it to the ViewModel

We can implement the View after the unit test for the ViewModel passed. I suggest to create a FXML-file by using the Scene Builder first. Now we create the JavaFX-Controller which is also part of the View layer in MVVM. You can view both files in the next code box by clicking on the tabs of the box. Let’s connect the View and the ViewModel by data binding, which ensures the propagation of changes in the properties. As we want the login button to be deactivated when there is no input in the text fields, we have to bind the disableProperty() of the login button to the isLoginPossibleProperty() in the ViewModel.

Login with Scene Builder

public class LoginView {

	@FXML
	private TextField userNameTextField;

	@FXML
	private PasswordField passwordTextField;

	@FXML
	private Button loginButton;

	@FXML
	void initialize() {
                //Create the ViewModel - In production this should be done by dependency injection
		LoginViewModel loginViewModel = new LoginViewModel();
                //Connect the ViewModel
		userNameTextField.textProperty().bindBidirectional(loginViewModel.userNameProperty());
		passwordTextField.textProperty().bindBidirectional(loginViewModel.passwordProperty());
		loginButton.disableProperty().bind(loginViewModel.isLoginPossibleProperty().not());
	}
}
<AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.saxsys.mvvm.LoginView">
 <children>
  <VBox alignment="CENTER_RIGHT" spacing="10.0">
   <children>
    <TextField fx:id="userNameTextField" alignment="BOTTOM_RIGHT" promptText="Username" />
    <PasswordField fx:id="passwordTextField" alignment="CENTER_RIGHT" promptText="Password" />
    <Button fx:id="loginButton" mnemonicParsing="false" onAction="#onLoginButtonPressed" text="Login" textAlignment="CENTER" />
   </children>
   <padding>
    <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
   </padding>
  </VBox>
 </children>
 <opaqueInsets>
 <Insets />
 </opaqueInsets>
</AnchorPane>

If the user enters a username or password the changes will be propagated by data binding from the View to the ViewModel. The ViewModel reevaluates the defined binding of the isLoginPossibleProperty(). A possible change of the isLoginPossibleProperty()  will be propagated to the disabledProperty() of the loginButton .

As you can see, we removed every logic from the view. The view is stupid and the behavior testable.

The example can be found on GitHub.

FXML composition – how to get the controller of an included FXML?

The possibility to declare the UI with an FXML in JavaFX is pretty cool. In bigger applications you have to split the views in different FXMLs which allows you to reuse and compose them in complex FXMLs. Therefore you have to use the  <fx:include>  statement.

The include enables you to get the reference of the embedded view element in your FXML-controller class. Be sure that you use the value of fx:id as the name of the member in the controller class. The FXMLLoader will pass the reference once the View was created.

[...]
<fx:include fx:id="embeddedView" source="SomeOtherView.fxml"/>
[...]
class AViewController{
    @FXML
    Parent embeddedView; //fx:id value as Variable name -> JavaFX injects the reference of the root element in the included FXML
}

Ok, we have the reference of the view element, but how can we get the reference of the embeddedView’s controller?  The solution is a naming convention which you can find here.


Short example (GitHub)

First Step: Create the FXML and the Java Controller which we want to embed:

 <AnchorPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="de.saxsys.fxml.RedView">
 <children>
 <Pane prefHeight="200.0" prefWidth="200.0">
 <children>
 <Button fx:id="button1" layoutX="68.0" layoutY="50.0" mnemonicParsing="false" text="Button" />
 <Button fx:id="button2" layoutX="68.0" layoutY="121.0" mnemonicParsing="false" text="Button" />
 </children>
 </Pane>
 </children>
</AnchorPane>
public class RedView {

    @FXML
    private Button button2;

    @FXML
    private Button button1;

    public void foo(String foo) {
       System.out.println(foo);
    }
}
redview

RedView.fxml

Second Step: Create the View which should embed the RedView.fxml. It’s not problem to do this using the Scene Builder. If you are using the developer preview of the Scene Builder 2, you may have problems to set an fx:id to the included elements – in this case add the fx:id attribute directly in the fxml code.

Add

Include FXML files using the SceneBuilder – Don’t forget to set the fx:id to the included element

 <VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.saxsys.fxml.MainView">
 <children>
 <!--Some element -->
 <Circle radius="100.0" stroke="BLACK" strokeType="INSIDE" />
 <!--our included FXML -->
 <fx:include fx:id="embeddedRedView" source="RedView.fxml" />
 <!--Some other element -->
 <Circle radius="100.0" stroke="BLACK" strokeType="INSIDE" />
 </children>
</VBox>
public class MainView {

	@FXML
	private Parent embeddedRedView; //embeddedElement

	@FXML
	private RedView embeddedRedViewController; // $embeddedElement+Controller

	public void initialize() {
		System.out.println(embeddedRedViewController);
		System.out.println(embeddedRedView);
                embeddedRedViewController.foo("It works"); //Console print "It works"
	}
}
Added

Embedded RedView.fxml into MainView.fxml using the Scene Builder

As you can see, we can get the reference of the nested controller by appending the word Controller in addition to the variable name of the embedded element.

public class Starter extends Application {

	@Override
	public void start(Stage stage) throws Exception {

		Parent parent = FXMLLoader.load(getClass().getResource("MainView.fxml"));
		Scene scene = new Scene(parent);
		stage.setScene(scene);
		stage.show();
	}

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

}

Let’s try it – create an JavaFX application which loads the MainView.fxml and you’ll see the printed message on the console, which prooves that we accessed the reference to the FXML-controller of the embedded FXML element.

Example on GitHub