My current passion is programming of JavaFX applications. A very common problem, not only when developing with JavaFX, is the implementation and testing of asynchronous functionality.
Developing and testing components can be quite straightforward (following a frontend specific pattern like MVVM) as long as execution is synchronous. A bit more complicated is testing classes that have dependencies on other classes which need to be processed asynchronous to not block the execution. Typically, UI programming provides examples for that see the figure.
Frontend logic (green box) implements certain use cases that can be triggered from different roles (e. g. users) or involves those. Parts of the frontend logic of some use cases rely on a backend service (red box). Often frontend execution is bound to a UI thread (a single thread) - also in case of JavaFX. If it is blocked by long-lasting operations, the application’s UI freezes. Therefore all blocking operations, as for instance calls of backend services, should be executed in a separate thread. It’s not seldom that service interfaces work synchronously (blocking).
For the frontend logic to be tested we consider different aspects:
- First a frontend component should be tested at the level of the unit, so we can ensure the part of the use case the component is responsible for is implemented correctly. To exclude further sources of errors and to test only the unit’s logic all environment elements (dependencies) need to be mocked. For the unit test level the way of execution, i. e. asynchronous execution, doesn’t matter. Rather, is the focus on the expected reaction to the results of the backend service.
- Beyond that the aspect of proper integration of the unit with its environment and the correct behaviour of asynchronous execution needs to be ensured. That is, the process flow of the fronted has to integrate with the asynchronous called backend service correctly.
Starting Point
At the beginning I mentioned that there are different approaches to implement asynchronous execution. In the context of JavaFX the closest idea is to use javafx.concurrent.Service. javafx.concurrent.Service and also javafx.concurrent.Task implement the Worker-Interface which brings a lot of convenience for communicating the state of the execution between UI and background thread.A colleague of mine gave a very interesting approach to that problem on his blog. In particular with testing Services there is the problem, that his solution seems to work only for a single call of such a service. The reason seems to be a suspicious thread check in the implementation of Service which is also named in the Javadoc at the top of the class, but not justified or explained:
void checkThread() { if (startedOnce && !isFxApplicationThread()) { throw new IllegalStateException("Service must only be used from the FX Application Thread"); } }
This is the reason why this works for simple cases. That is, when you only have to call a service once in a test case. However, when use cases need to be tested that come with state multiple service calls can be necessary to create or keep that state. At that point it became clear that this thread check logic is responsible for failing the tests a service is involved. The solution pattern of my colleague doesn't work for such cases.
My Use Case
To get the discussion a little bit more concrete, a class ClassToTest is given in the following code listing describing a use case (anyUseCase). For example, ClassToTest could be a ViewModel class (MVVM) or a Controller class (MVC) - according to the pattern.public class ClassToTest { private Service<string> service = new Service() { @Override protected Task createTask() { return new Task() { @Override protected String call() throws Exception { return new SomeService().longLastingOperation(); } }; } }; public void anyUseCase(StringProperty resultStringProperty, IntegerProperty progressProperty) { progressProperty.setValue(-1); service.setOnSucceeded(e -> { resultStringProperty.setValue(service.getValue()); progressProperty.setValue(1); }); service.setOnFailed(e -> { resultStringProperty.setValue("An error occurred: no result"); progressProperty.setValue(1); }); service.restart(); // use restart when it was started before } }
It uses a Service to execute the longLastingOperation asynchronous. So the UI thread runs on without any blocking. The long-lasting operation is started by the call method of a Task which in turn is created by Service.
Service is used for processes that need to be executed asynchronous repeatedly. For that, the Service uses a single Task instance to execute a process asynchronous once. That is, Service can be reused, Task needs to be recreated for a new execution.
Optionally, callbacks can be set via setOnSucceeded and setOnFailed to deal with the results of the Service’s execution. When the execution of the long-lasting operation succeeds, the result shall be used for the a new value of the resultStringProperty. This property is bound to a label in the UI, so the result is shown to the user. Otherwise, in case of exception, the error shall be signaled to the user by setting an error message to the same property.
The problematic test
Here the JfxRunner is used as the test runner which provides the test with an JavaFX Application Thread (the UI thread). Looking at the following test shows that multiple calls to the used service causes errors in the test execution due to the checkThread method in the Service class.// Using the solution pattern given by http://blog.buildpath.de/how-to-test-javafx-services/ @RunWith(JfxRunner.class) public class ClassToTestTest { @Test public void testAnyBusinessCaseCallingTwice() throws ExecutionException, InterruptedException { StringProperty resultProperty = new SimpleStringProperty(); IntegerProperty progressProperty = new SimpleIntegerProperty(0); // first call succeeds CompletableFuture longLastingOperationFuture1 = newFuture(progressProperty); cut.execLongLastingOperation(resultProperty, progressProperty); longLastingOperationFuture1.get(); assertEquals("An expensive result", resultProperty.get()); assertEquals(1, progressProperty.get()); // second call fails as because of {@link Service#checkThread} CompletableFuture longLastingOperationFuture2 = newFuture(progressProperty); cut.execLongLastingOperation(resultProperty, progressProperty); longLastingOperationFuture2.get(); assertEquals("An expensive result", resultProperty.get()); assertEquals(1, progressProperty.get()); } private CompletableFuture newFuture(IntegerProperty progressProperty) { CompletableFuture completableFuture = new CompletableFuture(); progressProperty.addListener((b, o, n) -> { if (n.intValue() == 1) { completableFuture.complete(null); } }); return completableFuture; } }
The first call of the service passes without problems. But the second one fails.
First solution idea
If a class needs to be unit-tested it is necessary to mock its dependencies. A “simple” way to achieve this without a framework is to extend the dependency class (create a sub-class) and overwrite methods - according to the behavior expected by the test.First of all, the idea was to use only the class javafx.concurrent.Task for asynchronous execution, so there would be no problem to test the behavior. We would simply sub-class Task and overwrite the call method whereas the trick is to change the visibility from protected to public. This gives us the ability to start Task synchronous and to stay in the current thread (unit test thread). Only for testing purposes the execution stays synchronously.
The problematic point is, that further important methods, needed for working with Tasks, are declared as final. For instance, the method setValue is final and also private. This takes us any possibility to overwrite it and to mock it.
Even, if the final-restrictions in Task would not exist, there is still the Service class simply starting Tasks asynchronous. Even, if the strategy overwriting the Service had worked, still there was the problem that a lot of methods needed to be overwritten. At the end the whole asynchronous logic of Service needed to be reimplemented synchronously. As this is a lot of overhead for only testing the reaction to the results of the service SomeService, we are trying to find another solution.
So the question is: How can we implement asynchronous execution of logic without resigning to test it?
Finding other solutions
To get the use cases tested, I talked to other colleagues who have experience with testing asynchronous methods. We came to the conclusion that we actually don't want to test the asynchronous execution by the Service, but the use case’s logic itself (at least in scope of unit testing).The following solution shows an implementation that is based on the idea of calculating a value by a java.util.concurrent.Callable and then either dealing with the result of success or failure. You can see, the abstraction uses a similar semantic as Service does.
public class AsyncExecution{ private Service service; public AsyncExecution onStart(Callable callable) { service = new Service() { @Override protected Task createTask() { return new Task() { @Override protected T call() throws Exception { return callable.call(); } }; } }; return this; } public AsyncExecution onSucceeded(EventHandler resultHandler) { service.setOnSucceeded(resultHandler); return this; } public AsyncExecution onFailed(EventHandler failureHandler) { service.setOnFailed(failureHandler); return this; } public void start() { service.restart(); } public T getValue() { return service.getValue(); } }
Using that wrapper construction is easy. See the following listing where the long-lasting operation to be processed asynchronous is just passed via a Callable Lambda expression as callback to the onStart method.
When the method anyBusinessCase is called the same is done for success and failure callbacks. Then the asynchronous execution is started. The start method is implemented using Service for that purpose.
public class ClassToTest { private final AsyncExecutionasyncExecution; public ClassToTest(AsyncExecution asyncExecution) { this.asyncExecution = asyncExecution; asyncExecution.onStart(() -> new SomeService().longLastingOperation()); } public void anyBusinessCase(StringProperty resultStringProperty, IntegerProperty progressProperty) { progressProperty.setValue(-1); asyncExecution.onSucceeded(e -> { resultStringProperty.setValue(asyncExecution.getValue()); progressProperty.setValue(1); }); asyncExecution.onFailed(e -> { resultStringProperty.setValue( "An error occurred: no result"); progressProperty.setValue(1); }); asyncExecution.start(); } }
The API is quite similar to the Service API. As all methods of the AsyncExecution are neither (package) private nor final, I'm able to mock these methods or the whole implementation to get the Callable executed synchronously in the test thread. The results are:
- the second test step where the Service is called the second times also passes - the test is green,
- and we do not need the JFXRunner anymore as we do not need a JavaFX application thread anymore.
Well, of course there are. For instance there is the CompletableFuture class and there is an Executor, that both can be used to influence the way of execution. Therefore we tried it in a short example. Below you can see an example implementation which also makes the tests passing green.
public class ClassToTest { public void anyBusinessCase(StringProperty resultStringProperty, IntegerProperty progressProperty) { progressProperty.setValue(-1); CompletableFuture.supplyAsync(() -> { try { return new SomeService().longLastingOperation(); } catch (InterruptedException e) { throw new RuntimeException(e); } }).whenComplete((resultString, exception) -> Platform.runLater(() -> { if (exception != null) resultStringProperty.setValue("An error occurred: no result"); else resultStringProperty.setValue(resultString); progressProperty.setValue(1); })); } }
The class respectively the concept of CompletableFuture seems to be very powerful - look at the huge amount of different methods for processing computation and evaluation of results. You can chain multiple futures together to form a pipeline of different processing steps — see this blogpost for detailed examples of the potential of CompletableFuture.
A definitively negative aspect of it is exception handling. Within the different processing methods of CompletableFuture you must not throw checked exceptions. You are forced to use (unchecked) runtime exceptions to transport errors to appended processing steps.
Furthermore, the appended processing seems to be executed outside the JavaFX Application Thread. This is why you can see the Platform.runLater delegation in my example. The documentation on the part of Oracle could be a bit more comprehensive to better get to know the different possibilities and the usage of that class.
As bad testable and replaceable the classes may be, at the same time this shows the right to exist for javafx.concurrent.Service and javafx.concurrent.Task: the automatic synchronization of results (and other values) between background and UI thread.
Conclusion
As you can see, the usage of CompletableFuture (in the given environment with JavaFX Application Thread, properties and checked exceptions) isn’t really easy and only conditionally more elegant. On the opposite is the question how elegant service APIs with checked exceptions are - indeed they aren't to find rarely.Whether to choose CompletableFuture, the JavaFX Concurrency API or something else depends on the needs of the project and own preferences. For frontend logic written in JavaFX it is obvious to choose the JavaFX Concurrency API. So much for the question which asynchronous implementations could be used.
Another aspect was to find a way to get frontend logic under test which uses asynchronous execution. When trying to find a solution for the named problem I mentioned that we could also subclass and overwrite Task and / or Service to get the execution synchronous. For latter this is quite a lot of work. Nevertheless a complete synchronous implementation of Service and Task would be nice to avoid such own workaround constructions.
Indeed, my preferred approach of wrapping the specific asynchronous execution delimits the usage of all the other features from the Worker-Interface (properties for messages, progress, etc.), in the code listing you can see the progress is set manually.
As the most of those features aren’t needed, this solution is sufficient for exactly this case and less labor-intensive. So it’s a trade-off.
To comprehend the shown example you can see the source code on Github. In the example application you will also find a small user interface that demonstrates the behavior and the consequences of the implemented threading approach. I have tried to make a commit for each step of problem occurrence and solution.