Building a Windows Installer for a JavaFX application

GitHub

After finishing the development of my current project, I started on the final task – building the installers. The Mac process was relatively painless but the Windows process was another story altogether. I came up with a workable solution, after a lot of trial and error, aided by some good blog posts here and here.

Caveat: This approach relies on Exe4j – a commercial product, but you can work with the free demo version. A standard license is 69 USD, and no, I don’t work there.

The Goal

Create an Windows 8+ installer for my JavaFX demo app 3 with:

  • an embedded JDK11+ custom runtime (and therefore using Java 9 modules)
  • Ability to package non-modular java applications
  • An executable that behaves like a Windows application
  • the usual installation features
  • No expensive installer tool licenses
  • Uninstall capability

The Process At a Glance

Below is the process for Windows and Mac. I will talk about the Windows process in this post. For the Mac process shown below, the java packager used is a backport provided by Gluon that works with JDK 11 onwards.

I will discuss the Windows process split into 5 steps:

  1. Define the installation directory
  2. Build the jar and add dependencies
  3. Create the install image
  4. Create the executable
  5. Create the installer

Setup

Step 1 – Define the installation directory

Nothing needs to be done here, just a description of the planned install directory for the installation process:

When the install image is ready, (installBuild directory) it will look like this:

  • lib/ – All the jars are placed here
  • jre/- The custom java runtime
  • javafx-app-1-1.0.0.jar – application jar file
  • JavaFxApp1.exe – executable

After installation on another machine, the install will also have this structure.

This will be created in the root of javafx-app-1 repository in the installBuild directory.

Step 2 – Build the jar and add dependencies

This stage uses gradlew tasks to build and package the entire repo (applications 1,2 and 3) which is cheating a bit, but simplifies the explanation. We will only start app3 in the final install executable.

So, run the following tasks in javafx-app-1 project root directory. They are nothing special:

gradlew jar
gradlew collateInstall

This will result in the following directory structure:

I prefer having the application jar file sitting on its own, rather than with the other jar files.

Step 3 – Create the Install Image

We now need to add the embedded custom java runtime to the installBuild directory. After this stage is completed, the installBuild directory should have everything required to start the application.

Step 3a – Find Modules Dependencies

We need to get a list of java 9 modules that the application and it’s dependencies use. This can be done with the JDK jdeps utility. I have wrapped this in the gradle task called listModules. When you run this task the output is a bit messy, and you need to ‘pick out’ the modules from the list

When you run:

gradlew listModules

You will get output as shown below (red highlights are mine). The highlighted modules will be needed for the next step. Since this is a dependency graph, you just traverse each entry from left to right until you hit a JDK module not on your mental list. Tag it red and continue. At the end add all the red modules to the dependencies.txt file. Since my app is a non-modular one, I suspect that is why the output is not a single, tidy list of modules.

Step 3b – Update dependencies.txt

The file dependencies.txt in the repo root directory contains a list of dependencies. It is used as input to the next step. I put it in a file for simplicity, as parsing the jdeps output is a manual process for now.

So I added all the red modules from previous step into dependencies.txt – you don’t need to. It is a single line of text:

java.base,java.xml,java.desktop,java.scripting,java.logging,javafx.base,javafx.graphics,javafx.controls,javafx.fxml

Step 3c – Generate the Custom Java Runtime

To generate a custom java runtime, we use the JDK jlink tool. It requires a list of modules your application requires and can even generate a launcher script. I have wrapped the jlink call into the buildRuntime task. This task puts the runtime into the installBuild/jre directory.

run it from the git repository root directory:

gradlew buildRuntime

After it’s done, that’s it – nothing more to do, other than to test it. You need to make sure you did not miss any modules.

The installBuild directory should now look like this:

Step 3d – Test Install Image

At this stage, the installBuild directory should have every thing required to run the application – completely standalone and at 45MB, a lot smaller than the full JDK.

Create a script like the following Windows batch command in the repository root directory:

installBuild\jre\bin\java.exe -cp installBuild/javafx-app-1-1.0.0.jar;installBuild/lib/* org.epistatic.app3.Main

when you run this script, it should start the application.

app3 running on Windows 10

Step 4 – Create the Executable

Now that the application is ready, we want to create a proper Windows executable. There are several ways of doing this:

  • Tools like launch4j, exe4j
  • Java Packager (Windows backport for JDK 11 here)
  • Write your own Java Launcher (an ancient, but useful article here)

After some experimentation, I chose Exe4j because it was inexpensive, and supported Java 9 modules. Java packager worked but was hard to configure (on Windows only – the Mac version works well) and the generated runtime and directory structure was messy, IMHO. I may try again with Java packager on Windows in the near future – maybe I missed something. Writing your own Java Launcher was only a consideration before I discovered Exe4j (distinct from the considerably more expensive Install4j), and found some old C code that looked like a feasible starting point.

I have created an Exe4j configuration file which you open with Exe4J and then generate the executable. It’s pretty easy and I got it working after only a small amount of trial and error. The key issue is to make all the paths relative to the exe4j file, making it portable.

Once you generate the executable (Step 9 in the Exe4J tool), your installBuild directory should now look like this:

Step 5 – Create the installer

The final step is to now create the installer. This will be done using Innosetup, a free windows tool for creator. Innosetup was the third or fourth installer tool I have used over the years and I found it wonderfully easy to use. I finished the installation in about an hour, which is astounding for a newbie. I take no credit for brilliance – all kudos goes to the tool. I was so impressed I donated some money to the project two minutes after I finished.

The Innosetup script is very brief:

Creating the installer was very simple in Innosetup. Click the generate button and it’s done. The installer is created in the installers directory.

And that’s it. All done.

More to Do

Innosetup supports the Windows signtool, which is necessary to sign your application with Microsoft certificates, proving your application comes from a valid software vendor (similar to the signing process with Apple’s Developer program). The installer above does not have this setup.

Also, the application we have packaged does not have any configuration, but any real application most likely will have. This would need to be installed, preserved between upgrades, and should be removable as well.

Finally, the process of upgrading older software versions may need to be handled differently, as the installer above overwrites any previous install.

Shutting Down from the OSX Application Menu

On OS X, there are two kinds of menus:

  1.   Application-specific ones you define yourself
  2.   The Application menu all applications get from OS X
The Application Menu

This is a non-issue on Linux and Windows, as there is no application menu. You create your own “quit application” menu item in JavaFX to shutdown you application and that’s it.

Unfortunately, JavaFX has limited support for configuring or using the OS X application menu. So what can you do with the application menu on OS X?

  1. Set the menu name (via tools like the javapackager tool)
  2. Process some events and add handlers via Apple specific JDK packages (via com.apple.eawt package)
  3. Use the JavaFX stage.setOnCloseRequest to handle the quit event

I will describe how to add a CloseRequest handler to application’s primary stage, as this took me the longest time to figure out how to do. This will enable you to shutdown your JavaFX Application when it has been installed as a real OS X Application. Once you know how, it’s dead simple. The only reason for this blog post is that it took me longer than expected to find this out. Maybe it was a bad day, or maybe I’m just an idiot. Either way, if I had some trouble getting to the bottom of it, no doubt someone else did as well.

In my flimsy defence, I had only done something similar in Swing before, and a search on StackOverflow resulted in questions related to, but not exactly answering the question.

Here is the logic for your main application class. That’s it. Embarrassing really.

 override fun start(stage: Stage) { 
 ...
  stage.setOnCloseRequest { 
    shutdown() 
  } 
  ...
}

fun shutdown() { 
  // your additional shutdown logic
  Platform.exit()
}

JavaFX appears to know which operating system is running and automatically uses the system menu bar if possible (unlike Swing). So, no need for code like this anymore:

if(System.getProperty("os.name").contains("mac"))
   menuBar.isUseSystemMenuBartrue 
else
   menuBar.isUseSystemMenuBarfalse 

Other Implications

I took a look at a few other OS X applications, and confirmed they only had a Quit Application menu item in the application menu, and nowhere else. I spend so much time hitting the ⌘-Q key (a habit learned way back in 1985), I never pay much attention to the quit menu items.

So, if you have a ‘Quit’ menu item in you JavaFX menu (expected for operating systems other than OS X), don’t show it on OS X – you only need it for Windows and Linux versions.

JavaFX, Kotlin and Spring Framework – Complete

github repository

This blog post fleshes out a previous, much briefer post on adding Spring Framework to the JavaFX and Kotlin mix. I took my original multi-controller application and converted all the JavaFX controllers into Spring Beans.  All the beans are initialised in a configuration class, and allowed me to refactor the controllers to use constructor based injection.

beans.png

Spring Controller Initialisation

All the controllers are created by Spring in a configuration class. Also the EventBus is also a bean and injected into all the controllers.

@Configuration
open class ApplicationConfig {
@Bean
open fun applicationController(fileListController: FileListController,
filePropertiesController: FilePropertiesController,
fileDataController: FileDataController): ApplicationController {
return ApplicationController(fileListController, fileDataController, filePropertiesController)
}

@Bean
open fun eventBus(): EventBus {
return EventBus()
}

@Bean
open fun fileDataController(eventBus: EventBus): FileDataController {
return FileDataController(eventBus)
}

@Bean
open fun fileListController(eventBus: EventBus): FileListController {
return FileListController(eventBus)
}

@Bean
open fun filePropertiesController(eventBus: EventBus): FilePropertiesController {
return FilePropertiesController(eventBus)
}
}

So the Application Controller is now much simpler, taking all the other controllers as constructor arguments.

class ApplicationController(private val fileListController: FileListController, private val fileDataController: FileDataController, private val filePropertiesController: FilePropertiesController) {
...
}

Application Startup

Application startup now needs to create a Spring Application Context and inject the application controller into the FXML Loader.

class Main : Application() {
private val context: AnnotationConfigApplicationContext = AnnotationConfigApplicationContext(ApplicationConfig::class.java)

@Throws(Exception::class)
override fun start(primaryStage: Stage) {
val controller = context.getBean("applicationController") as ApplicationController
val loader = FXMLLoader(javaClass.getResource("/springkotlin.fxml"))
loader.setController(controller)
...
}
}

And that’s it! The full source code is here.

JavaFX, Kotlin and Spring Framework

This is a brief post before the Easter break. In a spasm of productivity, I wrote a sample application which upgrades my previous application with multiple controllers to setup application services as Spring beans. These are injected into controllers as required.

The controllers delegate work to these service beans, which may be shared between controllers.

Not finished yet, but the sample application works.

Decoupled Controllers and a Simple Event-Driven Design

app3 in Github

This post is about using multiple controllers inside an application, whose composition is determined dynamically, so that all the FXML files are completely independent of each other. This differs a bit from the idea of nested controllers where the application FXML ‘includes’ component FXML files via the <fx:include> tag. In practice, I don’t think there is much between them. If in doubt, go with the nested controller option, as it’s a bit simpler.

I first discovered the method detailed in this post through ignorance, before realising nested controllers were even possible. I still prefer my way over nested controllers, but I am weird.

App3 in the repository allows you to drag files into a ListView which then fires events processed by other controllers to update UI elements. This app has 4 controllers, one ‘application’ controller which manages the while UI on  behalf of the application, and three specific controllers:

  • FileListController – Handles a list of files, and supports dragging from the desktop
  • FilePropertiesController – responsible for displaying a single file’s properties
  • FileDataController – responsible for displaying a single file’s contents.

All the File* controllers communicate via a simple set of File Events, managed by Google Guava’s EventBus. To keep the sample code manageable, the events only flow one-way from the FileListController to the other controllers.

For complex applications, it’s best to partition the user interface elements into separate controllers, each controlling a particular set of functionality. How you slice and dice your user interface is a strictly personal affair. Once you do that, you realise that each FXML-controller pairing lives in splendid isolation from the rest of your application (assuming you don’t stuff your controllers with fields imported from everywhere).

This quickly becomes an issue for application-wide functions that span multiple views. Here is where an events-driven approach comes to the rescue.

I will discuss a simpler architecture for my first post on multi-controller applications, whereby controller created events are published to other events subscribers, who also happen to be controllers. Future posts will address more realistic layered architectures and how these can be made to use events.

Key Points

  • A single event bus for the entire application
  • No ‘service’ or ‘logic’ layer for controllers to delegate to
  • Controllers are decoupled from each other
  • Controllers are decoupled from FXML

Key Point – A Single Event Bus

For application-wide messaging, a single bus is all that is necessary. Some complex controllers (dialogs for example) may require their own dedicated event bus, but I won’t discuss that in this post.

The event bus is initialised in the ApplicationController – which is the top level controller managing the application UI and all other controllers. All the other controllers are constructed with this single EventBus object.

class ApplicationController { 
   ...
 ...
   // this bus is used application wide
   private val applicationEventBus = EventBus()
   private val fileListController = FileListController(applicationEventBus)
   private val fileDataController = FileDataController(applicationEventBus)
   private val filePropertiesController = FilePropertiesController(applicationEventBus)
   ...
}

Key – No Service/Logic Layer

For this simple example, there is no service or logic layer, as the focus of this application is to demonstrate how to load multiple controllers and how to use an basic event-driven design for controller communication. Any real application would use controllers that delegate to some sort of service layer.

Key – Controllers Are Decoupled From Each Other

Since controllers only interact via event messages, the only information that passed between them is the event itself (“A File was Added”) and any model object inside the event. Events can contain data, but this should be part of the application domain/data model and NOT controller specific.

Key – Controllers Are Decoupled From FXML

Defining the controller class in the FXML mandates a default constructor, requires a setter based dependency injection approach. This works, but I prefer constructor based dependency injection to ensure controllers are always in a consistent state – one less thing to get wrong. This makes unit testing controllers more straightforward as well.

Interestingly, this could also mean one controller is mapped dynamically to multiple FXML files – something I have never needed to do.

The ApplicationController class has a simple method setupController() for loading FXML files and linking them to a controller. This is done from the JavaFX method initialize().

@FXML
fun initialize() {
    ...
    val fileListPane = setupController("/app3/fileList.fxml", fileListController)
    val filePropertiesPane = setupController("/app3/fileProperties.fxml", filePropertiesController)
    val fileDataPane = setupController("/app3/fileData.fxml", fileDataController)
    ...
}

/**
* Utility function to load FXML and link it to its controller
*/
private fun setupController(fxmlPath: String, controller: EventAwareController): Pane {
   val loader = FXMLLoader(javaClass.getResource(fxmlPath))
   loader.setController(controller)
   return loader.load<Pane>()
}

Code Setup

Event Bus – Controller Registration

Each controller inherits from the EventAwareController class whose function is to store a reference to the event bus (for event publishing) and to register the controller with the evenBus (for event subscribing). Inheritance is just one way of doing this, but enables all subclasses to publish events via the EventBus.post() method.

abstract class EventAwareController(val eventBus: EventBus){ 
   init{
      eventBus.register(this)
   }
}

Event Definitions

The events themselves are defined as standalone classes which both inherit from an abstract SingleFileEvent:

abstract class SingleFileEvent(val file: File) 

class FileAddedEvent(file: File) : SingleFileEvent(file)

class FileSelectedEvent(file: File) : SingleFileEvent(file)

If necessary, all events can implement some sort of event interface as well. Up to you. I generally use different, layered event class hierarchies to enable event propagation and translation between application layers.

Event Handlers (Subscribers)

In this application, all the event handlers are controllers. For each event of interest, a controller implements a subscriber method for that event type, annotated with the @Subscribe annotation from the Guava Libraries Event Bus.

In the example below, a single controller handles two types of events, which happen to have the same implementation. 

@Subscribe
fun handleFileAdded(e: FileAddedEvent) {
   println("FileDataController processing FileAddedEvent")
   loadAndDisplayContents(e.file)
}

@Subscribe
fun handleFileSelectionChanged(e: FileSelectedEvent) {
   println("FileDataController processing FileSelectedEvent")
   loadAndDisplayContents(e.file)
}

Since both are SingleFileEvent subclasses, one handler could have processed both events, given they both do the same thing, but I think having them split is a bit neater.  Here is a possible handler for all types of SingleFileEvent events.

@Subscribe
fun imaginaryHandleAllFileEvents(e: SingleFileEvent) {
   println("Some subscriber processing all SingleFileEvents here")
   loadAndDisplayContents(e.file)
...
}

Events In Action

A FileEvent is Published

A File Event is created when:

  1. A file is dragged into the ListView (FileAddedEvent)
  2. An item in the ListView is selected (FileSelectedEvent)

Here are the two code sections that generate these events (complete file):

File Drag ==> FileAddedEvent

fileListView.onDragDropped = EventHandler { event -> 
    ...
    eventBus.post(FileAddedEvent(firstFile))
  ...
}

ListView Selection ==> FileSelectedEvent

// fire event on EventBus every time selection changes
fileListView.selectionModel.selectedItemProperty().addListener { _, _, newSelection ->
...

eventBus.post(FileSelectedEvent(newSelection))
...
}
}

The Event is Processed

For the FileAddedEvent, two controllers process the events in different ways:

  • FileDataController loads the file content and displays it
  • FilePropertiesController displays basic file properties

Here are the same event subscribers for both Controllers:

// from FileDataController
@Subscribe
fun handleFileAdded(e: FileAddedEvent) {
   println("FileDataController processing FileAddedEvent")
   loadAndDisplayContents(e.file)
}

// from FilePropertiesController
@Subscribe
fun handleFileAdded(e: FileAddedEvent){
  println("FilePropertiesController processing FileAddedEvent")
  nameLabel.text = e.file.name
  pathLabel.text = e.file.path
  sizeLabel.text = e.file.length().toString()
}

Summary

This post focused on a single application, app3, in my repo. Like the other applications in the repo, you run it with gradlew (checkout the repository README.md for setup details):

gradlew runApp3 

Hopefully I have given you an understanding of a way to separate your controllers from each other, and from any particular visual representation in FXML, and introduced the excellent EventBus library along the way.

javafx-gradle-plugin

After tweeting my blog posts on Twitter, I got a reply from the cofounder of Gluon no less (Eugene Ryzhikov) who suggested I take a look at the javafx-gradle-plugin. I’m glad I did – it has cleaned up my gradle file nicely so that it looks less abnormal.

I think I did it right.

Old Gradle Dependencies

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
compile name: "javafx.fxml" // WEIRD
compile name: "javafx.controls" // WEIRD
compile name: "javafx.base" // WEIRD
compile name: "javafx.graphics" // WEIRD
compile "com.google.guava:guava:27.1-jre"

testCompile "org.jetbrains.kotlin:kotlin-reflect:$KOTLIN_VERSION"
testCompile "org.assertj:assertj-core:3.11.1"
testCompile "org.testfx:testfx-core:4.0.15-alpha"
testCompile "org.testfx:testfx-junit:4.0.15-alpha"
}

New Gradle Dependencies

javafx {
modules = [ 'javafx.controls', 'javafx.fxml' ]
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
compile "com.google.guava:guava:27.1-jre"
// WEIRDNESS GONE
testCompile "org.jetbrains.kotlin:kotlin-reflect:$KOTLIN_VERSION"
testCompile "org.assertj:assertj-core:3.11.1"
testCompile "org.testfx:testfx-core:4.0.15-alpha"
testCompile "org.testfx:testfx-junit:4.0.15-alpha"
}

The full gradle file is here.

TestFx

The most well-known library for unit and integration testing JavaFX applications is TestFX. I have written some tests inside the application to run Junit-driven tests using TestFX – https://github.com/SergeMerzliakov/javafx-app-1.

TestFX works much like any other JUnit oriented testing library and to set it up you need to do 3 things:

  1. Add the testfx libraries as dependencies to build.gradle
  2. Make your Test classes inherit from org.testfx.framework.junit.ApplicationTest
  3. Implement the start() method to create the app to test
  4. Ensure all to-be-tested controls have a unique JavaFX Id

Point 4 above is optional but I think it is good practice.

SETUP

Step 1 – Add TestFx to build.gradle


dependencies {
    ....
    testCompile 'org.testfx:testfx-core:4.0.15-alpha'
    testCompile 'org.testfx:testfx-junit:4.0.15-alpha’
    ....
}

Nothing more to add here. We’ve all done this before.

Step 2 – Set Test classes to inherit from ApplicationTest

This adds TestFX magic juju to your test classes.

class MyClassTest : ApplicationTest() { 
  ...
  ...
}

Once again, nothing revolutionary so far. Sure, we are using up our inheritance ‘slot’, and maybe an annotation based approach would have been nicer, but such is life.

Step 3 – Implement the start() method

ApplicationTest has a single method you need to override, which is almost the same as the start method used to start a JavaFX Application. The example below creates a loader so that we can keep a reference to the FXML’s controller class, which is useful for testing.

...
private lateinit var controller: Controller

override fun start(stage: Stage?) {
  // We instantiate a loader directly so we can access the controller for testing
  val loader = FXMLLoader(javaClass.getResource(“/dir1/dir2/application.fxml"))
  val root = loader.load<VBox>()
  stage?.scene = Scene(root)

  // keep a reference to the controller for our tests
  controller = loader.getController()
  stage?.show()
}
...

Step 4 – Ensure Controls All Have Unique JavaFX Ids

You can do this is a few different ways:

  • SceneBuilder
  • In code
  • Hand editing the FXML file

Setting JavaFX Id inSceneBuilder

Just a screen shot for this one. Just have to know which text box to fill in.

JavaFX ID is the same as a CSS ID Selector. You know, like #greenButtonWithSharkLogo

Setting JavaFX Id in Code

@FXML
fun initialize() {
    ...
    tableView.id“tableView"
}

Setting JavaFX Id in FXML by Hand

<Button id="addItemButton" mnemonicParsing="false" onAction="#addItem" text="Add To List"> 
    <FlowPane.margin>
       <Insets right="10.0" />
    </FlowPane.margin>
</Button>

WRITING TESTS

This part is very much same as for writing any JUnit tests, but uses the TestFX robot helper functions to simulate a user.

The conceptual steps are:

  1. Find the control to test in the node graph 
  2. Perform some action on the control
  3. Check the results on the model and/or view

Step 1 – Find the Control to Test in the Node Graph

You use the org.testfx.api.FxRobotInterface.lookup() method to find the control you need. There are a few ways of doing this:

  • Using the text of a labelled node:
val cancelButton = lookup(“Cancel”).query<Button>()
  • Using the CSS class of a node:
val allButtons =  lookup(“.button”).queryAll<Button>()
  • Using the JavaFx id of a node:
val pinkButton = lookup(“#thirdPinkButtonOnTheLeft").query<Button>()
  • Using a whole CSS selector:
val helpButton = lookup(".small-button #helpButton").query<Button>()

I prefer the methods using the JavaFx id, as they can be made unique and less prone to CSS changes.

Step 2 – Perform Some Action on the Control

There are lots of these in org.testfx.api.FxRobotInterface. The most common being clickOn(), doubleClickOn(), write() and type().

At this point you can perform some actions, such as:

  • Pushing a button
clickOn(“#updateButton”)
  • Entering text in a field (once it is selected or has focus)
clickOn("#SomeTextField") 
write(“This is the text in the currently selected field”)
  • Hitting enter or tab key:
type(KeyCode.TAB) 
type(KeyCode.ENTER)

Step 3 – Check the Results

You can check the model, the view or both.

If the data model is an ObservableList<T>, checking both model and view containers is not really necessary, as the view gets updated automatically whenever you alter the contents of the model . This is AWESOME by the way! One line of code, and no more addThisListener() or addThatListener()……

If your model is a just a standard collection of some kind,  and you manually manage model-view synchronisation, then testing both model and views after UI operations makes sense. Any such data model tests probably belong in another test, and not lumped in with UI tests.

Checking the Model

// check model after removing an item from the list view
assertThat(controller.friends).hasSize(originalModelCount - 1)

Checking the View

// check ListView items reduced by one.
assertThat(friendView.items).hasSize(originalViewCount - 1)

A Complete Example

ItemTabIntegrationTest.kt

/** 
* Use Junit TestFX to integration test the application UI, so
* not unit tests per se.
* BetterApplicationTest is a subclass of TestFx class ApplicationTest (discussed below)
*/
class ItemTabIntegrationTest : BetterApplicationTest() {

 // Node Ids defined in the FXML file. Same as a CSS Id selector
  // Use these to uniquely identify JavaFX controls for testing
  companion object {
    const val LIST_DEMO_TAB = "#listDemoTab"
    const val ITEM_LIST_VIEW = "#itemListView"
    const val ENTER_ITEM_FIELD = "#enterItemField"
    const val ADD_ITEM_BUTTON = "#addItemButton"
 }

  private lateinit var controller: Controller

 override fun start(stage: Stage?) {
    // we instantiate a loader directly so we can access the controller for testing
    val loader = FXMLLoader(javaClass.getResource("/app1/app1.fxml"))
    val root = loader.load<VBox>()
    controller = loader.getController() // keep a reference for our tests
    stage?.scene = Scene(root)
    stage?.show()
 }

  @Test
  fun shouldAddItemToList() {
    val initialCount = controller.listModel.size


     //ensure correct Tab has focus
    clickOn(LIST_DEMO_TAB)


    //create new item for list - should appear at the end
    val newItem = "Zoo"
    clickOn(ENTER_ITEM_FIELD)
    write(newItem)
    type(KeyCode.TAB)
    clickOn(ADD_ITEM_BUTTON)

    // check model updated
    val modelCount = controller.listModel.size
    assertThat(modelCount).isEqualTo(initialCount + 1)

    // check view updated
    val viewCount = controller.itemListView.items.size

  // this method is in declared in the base class
    val zooListItem = getListViewRow(ITEM_LIST_VIEW, viewCount - 1)
    assertThat(zooListItem.text).isEqualTo(newItem)  }
}

BetterApplicationTest.kt

I have created a subclass of org.testfx.framework.junit.ApplicationTest and added some methods to manipulate listView and tableView data. All tests in app1 and app2 in the repo inherit from this. A better way may be to create a ViewTestUtils object with these methods in it, but you would need to pass in an instance of the ApplicationTest. 

Neither option excited me, so I went with the simplest option – subclassing. I am keen to fix this as I use these helper methods a lot, and the subclassing approach has limited traction across a real project with hundreds of tests.

open class BetterApplicationTest : ApplicationTest() { 
   /**
    * Helper function to get a row from a TableView
    *
    *  Creating a generic version of this function ==> fun <T:Any> getTableViewRow(viewId: String, row: Int): T
    *
    *  is very tricky and I did not quite succeed. Ah, so close...
    *
    *  see: https://stackoverflow.com/questions/43477903/instantiating-a-generic-type-in-kotlin
    */
   fun getTableViewRow(viewId: String, row: Int): SomeProperty {
      val tableView = lookup(viewId).query<TableView<SomeProperty>>()
      val cell1: TableCell<SomeProperty, String> = from(tableView).lookup(".table-cell").nth(row * 2).query()
      val cell2: TableCell<SomeProperty, String> = from(tableView).lookup(".table-cell").nth(row * 2 + 1).query()

      return SomeProperty(cell1.text, cell2.text)
   }

   /**
   * Helper function to get a row from a ListView.
    * Type T is the type of the ListView data model.
    */
   fun getListViewRow(viewId: String, row: Int): ListCell<String> {
      val listView = lookup(viewId).query<ListView<String>>()
      return from(listView).lookup(".list-cell").nth(row).query()
   }

   /**
   * Helper function to get a row starting the given text from a ListView.
    * Type T is the type of the ListView data model.
    *
    *  This method is a bit dodgy, but illustrates how to access listView row data
    *
    */
   fun <T> getListViewRowByFirstName(viewId: String, textToFind: String): ListCell<T>? {
      val listView = lookup(viewId).query<ListView<T>>()
      val cells = from(listView).lookup(".list-cell").queryAll<ListCell<T>>()
      // assumes type T has a toString method starting with first name!
      return cells.find { it.item.toString().toLowerCase().startsWith(textToFind.toLowerCase()) }
   }
}

DRAG AND DROP

Getting TestFX to test drag and drop operations is surprisingly easy. After wasting my youth on lots of fun and games with SWT and Swing, I mentally readied myself for an epic battle of wits and stamina, only to succeed very quickly. What a letdown.

So a snippet from one of Drag and Drop tests looks like this:

@Test 
fun shouldDragSingleFriendBetweenTwoListViews() {
   .....
   // drag a single friend from friendListView to PartyListView
   dragFriendToParty("Andrea")
   ......
}

private fun dragFriendToParty(firstName: String) {
   // find friend by first name via dodgy utility method
   val friend = getListViewRowByFirstName<Person>(FRIEND_LIST_VIEW, firstName)

 // drag and drop automation is just this line!
 drag(friend).dropTo(PARTY_LIST_VIEW)
}

That’s it. Just a single lines in the dragFriendToParty() method. Here is the whole unit test class in GitHub.

SUMMARY

Whilst it’s a bit strange at first, once you get your head around the TestFX way of doing things, it quickly becomes easy to write tests for your user interface, and will become an indispensable part of your automated test arsenal. TestFX can be used for unit testing individual components as well, even though the examples here focus on testing entire applications.

TestFX Resources

TestFX Robot functions: https://github.com/TestFX/TestFX/blob/master/docs/manual/component-robot.md

TestFX queries: https://github.com/TestFX/TestFX/wiki/Queries