From f77f31078b400a2006c5b81bec34a92216adcc7a Mon Sep 17 00:00:00 2001 From: Manuel Mauky Date: Tue, 14 Aug 2018 16:26:21 +0200 Subject: [PATCH 1/2] Fix #556 improve FxTestUtils to better support testing of FX code. improve documentation --- .../mvvmfx/testingutils/FxTestingUtils.java | 38 +++++++++-- .../testingutils/JfxToolkitExtension.java | 11 +++- .../testingutils/jfxrunner/JfxRunner.java | 14 ++-- .../jfxrunner/TestInJfxThread.java | 7 ++ .../mvvmfx/testingutils/Junit5Test.java | 66 +++++++++++++++++++ 5 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 mvvmfx-testing-utils/src/test/java/de/saxsys/mvvmfx/testingutils/Junit5Test.java diff --git a/mvvmfx-testing-utils/src/main/java/de/saxsys/mvvmfx/testingutils/FxTestingUtils.java b/mvvmfx-testing-utils/src/main/java/de/saxsys/mvvmfx/testingutils/FxTestingUtils.java index f54ddcb6f..5749338ee 100644 --- a/mvvmfx-testing-utils/src/main/java/de/saxsys/mvvmfx/testingutils/FxTestingUtils.java +++ b/mvvmfx-testing-utils/src/main/java/de/saxsys/mvvmfx/testingutils/FxTestingUtils.java @@ -8,22 +8,50 @@ import java.util.concurrent.TimeoutException; public class FxTestingUtils { - - + /** + * This method is used to execute some code on the JavaFX Thread. + * It is intended to be used in testing scenarios only because the internal exception + * handling is optimized for testing purposes. + * + * This method is blocking. It will execute the given code runnable and wait + * for it to be finished. However, a timeout of 1000 milliseconds is used. + * If you need another timeout value, use {@link #runInFXThread(Runnable, long)}. + * + * This method will catch exceptions produced by the execution of the code. + * If an {@link AssertionError} is thrown by the code (for example because an test assertion has failed) + * the assertion error will be rethrown by this method so that the JUnit test runner will properly handle it + * and show you the test failure. + * @param code the code to execute on the JavaFX Thread + */ public static void runInFXThread(Runnable code){ runInFXThread(code, 1000); } + /** + * See {@link #runInFXThread(Runnable)}. This method takes a timeout of milliseconds + * as second parameter. + * @param code the code to execute on the JavaFX Thread + * @param timeout the timeout to be used before the execution is canceled. + */ public static void runInFXThread(Runnable code, long timeout){ - CompletableFuture future = new CompletableFuture<>(); + CompletableFuture future = new CompletableFuture<>(); Platform.runLater(() -> { - code.run(); + try { + code.run(); + } catch (AssertionError e) { + future.complete(e); + return; + } future.complete(null); }); try { - future.get(timeout+50, TimeUnit.MILLISECONDS); + Object result = future.get(timeout + 50, TimeUnit.MILLISECONDS); + + if(result instanceof AssertionError) { + throw (AssertionError) result; + } } catch (InterruptedException | ExecutionException | TimeoutException e) { e.printStackTrace(); } diff --git a/mvvmfx-testing-utils/src/main/java/de/saxsys/mvvmfx/testingutils/JfxToolkitExtension.java b/mvvmfx-testing-utils/src/main/java/de/saxsys/mvvmfx/testingutils/JfxToolkitExtension.java index ff0f32a63..e70d42df2 100644 --- a/mvvmfx-testing-utils/src/main/java/de/saxsys/mvvmfx/testingutils/JfxToolkitExtension.java +++ b/mvvmfx-testing-utils/src/main/java/de/saxsys/mvvmfx/testingutils/JfxToolkitExtension.java @@ -4,8 +4,17 @@ import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; +/** + * This JUnit 5 extension can be used to start a JavaFX Thread before executing the tests. + * This way a JavaFX Thread will be available in your tests so that you can use Platform.runLater + * and similar constructs in your tests. + * + * However, this extension will not execute your Test method on the JavaFX Thread. + * To execute some test code on the JavaFX Thread you should use {@link FxTestingUtils#runInFXThread(Runnable)}. + */ public class JfxToolkitExtension implements BeforeAllCallback { - @Override public void beforeAll(ExtensionContext extensionContext) throws Exception { + @Override + public void beforeAll(ExtensionContext extensionContext) throws Exception { new JFXPanel(); } } diff --git a/mvvmfx-testing-utils/src/main/java/de/saxsys/mvvmfx/testingutils/jfxrunner/JfxRunner.java b/mvvmfx-testing-utils/src/main/java/de/saxsys/mvvmfx/testingutils/jfxrunner/JfxRunner.java index 9d975dced..1c8025f57 100644 --- a/mvvmfx-testing-utils/src/main/java/de/saxsys/mvvmfx/testingutils/jfxrunner/JfxRunner.java +++ b/mvvmfx-testing-utils/src/main/java/de/saxsys/mvvmfx/testingutils/jfxrunner/JfxRunner.java @@ -12,10 +12,16 @@ import org.junit.runners.model.InitializationError; /** - * This basic class runner ensures that JavaFx is running and then wraps all the - * runChild() calls in a Platform.runLater(). runChild() is called for each test - * that is run. By wrapping each call in the Platform.runLater() this ensures - * that the request is executed on the JavaFx thread. + * This JUnit 4 TestRunner can be used to start a JavaFX Thread for test execution. + * This way a JavaFX Thread will be available so that you can use Platform.runLater + * and similar constructs in your test code. + * + * However, this TestRunner alone won't execute your test method on the JavaFX Thread. + * To execute a test method on the JavaFX Thread you have to use the annotation {@link TestInJfxThread} + * together with this test runner. + * + * This solution only works for JUnit 4. For a solution for JUnit 5 please see {@link de.saxsys.mvvmfx.testingutils.JfxToolkitExtension}. + * */ public class JfxRunner extends BlockJUnit4ClassRunner { /** diff --git a/mvvmfx-testing-utils/src/main/java/de/saxsys/mvvmfx/testingutils/jfxrunner/TestInJfxThread.java b/mvvmfx-testing-utils/src/main/java/de/saxsys/mvvmfx/testingutils/jfxrunner/TestInJfxThread.java index c70275384..988277549 100644 --- a/mvvmfx-testing-utils/src/main/java/de/saxsys/mvvmfx/testingutils/jfxrunner/TestInJfxThread.java +++ b/mvvmfx-testing-utils/src/main/java/de/saxsys/mvvmfx/testingutils/jfxrunner/TestInJfxThread.java @@ -5,6 +5,13 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * This annotation can be used together with {@link JfxRunner} to execute + * an annotated test method on the JavaFX Thread. + * + * Please notices that this only works with JUnit 4. + * For a solution for JUnit 5 see {@link de.saxsys.mvvmfx.testingutils.FxTestingUtils#runInFXThread(Runnable)}. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface TestInJfxThread { diff --git a/mvvmfx-testing-utils/src/test/java/de/saxsys/mvvmfx/testingutils/Junit5Test.java b/mvvmfx-testing-utils/src/test/java/de/saxsys/mvvmfx/testingutils/Junit5Test.java new file mode 100644 index 000000000..2164bd128 --- /dev/null +++ b/mvvmfx-testing-utils/src/test/java/de/saxsys/mvvmfx/testingutils/Junit5Test.java @@ -0,0 +1,66 @@ +package de.saxsys.mvvmfx.testingutils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import java.util.concurrent.CompletableFuture; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import javafx.application.Platform; + + +class Junit5Test { + + @ExtendWith(JfxToolkitExtension.class) + @Nested + class OnFxThread { + @Test + void testFxThreadIsAvailable() throws Exception { + assertThat(Platform.isFxApplicationThread()).isFalse(); + CompletableFuture isInApplicationThread = new CompletableFuture<>(); + Platform.runLater(() -> { + isInApplicationThread.complete(Platform.isFxApplicationThread()); + }); + assertThat(isInApplicationThread.get()).isTrue(); + } + + + @Test + void testOnFxThreadSuccess () { + FxTestingUtils.runInFXThread(() -> { + assertThat(Platform.isFxApplicationThread()).isTrue(); + }); + } + + @Test + void testOnFxThreadFailed () { + Assertions.assertThrows(AssertionError.class, () -> { + FxTestingUtils.runInFXThread(() -> { + fail("some reason"); + }); + }, "some reason"); + } + } + + @Nested + class NotOnFxThread { + + @Test + void testFxThreadIsNotAvailable() { + assertThat(Platform.isFxApplicationThread()).isFalse(); + + Assertions.assertThrows(IllegalStateException.class, () -> { + Platform.runLater(() -> {}); + }, "Toolkit not initialized"); + } + + + } + + + +} From 2d228eb888beed94db8d1a327ee82220d7fe718c Mon Sep 17 00:00:00 2001 From: Manuel Mauky Date: Tue, 14 Aug 2018 16:35:43 +0200 Subject: [PATCH 2/2] fix failing tests when executed via maven --- mvvmfx-testing-utils/pom.xml | 12 ++++++++++++ .../de/saxsys/mvvmfx/testingutils/Junit5Test.java | 5 ----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/mvvmfx-testing-utils/pom.xml b/mvvmfx-testing-utils/pom.xml index 9f4a841e9..9b5d8498c 100644 --- a/mvvmfx-testing-utils/pom.xml +++ b/mvvmfx-testing-utils/pom.xml @@ -15,6 +15,18 @@ mvvmFX testing utils This module contains some utils for testing + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + + + eu.lestard diff --git a/mvvmfx-testing-utils/src/test/java/de/saxsys/mvvmfx/testingutils/Junit5Test.java b/mvvmfx-testing-utils/src/test/java/de/saxsys/mvvmfx/testingutils/Junit5Test.java index 2164bd128..2acfacfb8 100644 --- a/mvvmfx-testing-utils/src/test/java/de/saxsys/mvvmfx/testingutils/Junit5Test.java +++ b/mvvmfx-testing-utils/src/test/java/de/saxsys/mvvmfx/testingutils/Junit5Test.java @@ -57,10 +57,5 @@ void testFxThreadIsNotAvailable() { Platform.runLater(() -> {}); }, "Toolkit not initialized"); } - - } - - - }