UPDATE: turns out the JUnitParam tests are broken due to Issue #47, however there's an alternative for parameterized tests: Spock. Continue reading at the bottom to see the updated tests
UPDATE 2: JUnitParams 1.0.3 fixes the problem reported by Issue #47.
Today I read a post by Alexander Casall (@sialcasa) about How to test JavaFX Services and thought "maybe I can come up with an alternative using Griffon". So this post is the Griffon version to test out services and controllers in a JavaFX application.
The first thing I'd like to state is that's my conviction that services should be completely separate from UI aspects. In Alexander's example, the LoginService
creates an instanced of javafx.concurrent.Task in the body of the login()
method. This asserts that the execution always happens inside the UI thread. Hmmm. I agree that updating an observable model property (which appears to be the case for the updateMessage()
method) must happen inside the UI thread, however the service could be simpler and just be responsible for plain inputs and outputs.
Here's my (full) version for a LoginService
that performs the login operation according to the new design:
In Alexander's example, the service calls out to another component (clientApi
) to perform the actual login procedure. This API call may throw an exception if there's a network problem or because of another unforeseen condition. I decided to keep the example simpler and make a trivial username/password check in the service method itself.
Now, because the service is so simple you can test it out in the following manner
A few things of note
GriffonUnitRule
instantiates a dummyGriffonApplication
and performs dependency injection (similarly to a GuiceBerry enabled test).- an instance of
LoginService
is injected to theservice
field. - JUnitParams is used to parameterize the test. John Ferguson Smart (@wakaleo) just posted a great article title "Data-driven unit testing in Java" on this very topic.
Alright, the service is done. But we still have to build a functioning application, isn't it? Somehow, a component must make a call to LoginService
and update model properties inside the right thread. This is a job for a controller
, like the following one
for those new to Griffon there's a special feature regarding controller actions that you must be aware of, mainly, that they will be executed outside of the UI thread automatically. This behavior is configurable of course. In our case the login()
action is executed outside of the UI thread because it relies on the default configuration. This action performs the following tasks
- disable the
login
action (of typeJavaFXAction
). This action is bound to the login button. You can think ofJavaFXAction
as a similar abstraction asjavafx.action.Action
. A missing class in the JavaFX 8 API if you ask me. - notice that disabling the action happens inside the UI thread. The code makes use of the lambda expression syntax (which is nice!).
- invoke the
loginService.login()
with values coming from the model. How those values are placed in the model is of no concern to the controller, but we can assume they come from bindings made to UI components. - update another model property with the output of the
login
procedure. This update happens inside the UI thread too because we assume the property is bound to an UI component. - finally re-enabled the
login
action.
Testing this controller will require that we're able to handle asynchronous validations, precisely the problem that Alexander addresses in his post. But instead of using a custom JUnitRunner
and CompletableFuture
we'll leverage Griffon's test support coupled with an amazing testing utility called Awaitility. Here's how the test looks
This is another parameterized test using JUnitParams, another reason why we can't use a custom JUnitRunner like the one shown by Alexander. Instead we go with the simplest route in order to initialize the JavaFX toolkit: create an instance of javafx.embed.swing.JFXPanel
during class initialization. Waiting for a condition to be reached in an asynchronous way is quite trivial with Awaitility, as seen in line 48. I'd say the code is quite readable, woudln't you agree? The IsBlank
matchers is not provided by the Hamcrest, but it's simple to create
In order to be able to use both JunitParams and Awaitility we must define dependencies in the build file. Griffon uses Gradle by default (although you can use Maven too), so the build.gradle
file contains the following dependency entries
You may be wondering about the rest of the code, Model
and View
. Here's the model that shows usage of plain JavaFX properties
Definitely verbose. Here's hoping a Lombok extension is built in the future that can create such boiler-plate code for JavaFX properties. Next is the view, where we see the linking between model properties and UI components
The important bits happen inside the init()
method. This is where model properties are bound to their respective UI components. Controller actions are also connected to their intended targets. Notice that the view uses the @FXML
annotations to mark the injection points for UI components. This means it's SampleView
and not the SampleController
the one that receives the injections. This switch is automatically handled by the loadFromFXML()
method. Finally we see the FXML loaded by the view
This is a run-of-the-mill, SceneBuilder generated FXML file. The only thing that make look suspicious is the button's id. Griffon follows a naming convention to be able to connect controller actions to control targets. The following screenshots show the application as it appears after some login attempts
![]() |
![]() |
no login | blank entries |
---|---|
![]() |
![]() |
successful login | invalid entries |
And there you have it. And alternate way to write and test controller and services in JavaFX.
Keep on Groovying!
UPDATE: So JUnitParams has trouble due to Issue #47. Fortunately there's an alternative for parameterizing tests in the form of Spock. We can uses Spock's data driven feature methods to parameterize the tests for both LoginService
and SampleController
. I also took the liberty of updating LoginService
to use a delegate ClientApi
component to perform the actual login procedure. Here's how the LoginServiceSpec
looks like
Notice the usage of Spock's interactions. They're like regular mocks, but groovier This also shows one of the many ways to override a JSR-330 biding in a testcase, in this case the specification requires a mock of the
ClientApi
type.
Next is the SampleControllerSpec
, containing an almost identical setup as SampleControllerTest
, the main difference being how the parameterization is setup.
So there you go. If you're willing to branch out and try out other testing tools, such as Spock, your testing experience should be a more gratifying one.