From 9840f929af50a3841f02758db353064b21edb20c Mon Sep 17 00:00:00 2001 From: LeStegii Date: Wed, 15 May 2024 11:29:04 +0200 Subject: [PATCH 1/7] docs(testing): Add main section and subsections for controllers and setup --- docs/testing/1-setup.md | 47 +++++++++++ docs/testing/2-controllers.md | 83 +++++++++++++++++++ docs/testing/README.md | 10 +++ .../ludo/controller/IngameController.java | 6 +- 4 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 docs/testing/1-setup.md create mode 100644 docs/testing/2-controllers.md create mode 100644 docs/testing/README.md diff --git a/docs/testing/1-setup.md b/docs/testing/1-setup.md new file mode 100644 index 00000000..ff144750 --- /dev/null +++ b/docs/testing/1-setup.md @@ -0,0 +1,47 @@ +# Setup + +In order to properly test your application, it is recommended to use [TestFX](https://github.com/TestFX/TestFX) alongside [Mockito](https://github.com/mockito/mockito). +For a full explanation of both libraries, checkout their official documentation, as the following documentation will only cover a small part of what the projects have to offer. +## TestFX + +TestFX can be used to test the frontend of your application by checking if certain requirements are met, for example view elements being visible or having a certain property. + +Alongside TestFX, we also include Monocle which allows for headless testing without the app having to be open on your screen every time the tests are ran. + +```groovy + testImplementation group: 'org.testfx', name: 'testfx-junit5', version: testFxVersion + testImplementation group: 'org.testfx', name: 'openjfx-monocle', version: monocleVersion +``` + +To enable headless testing, the following lines can be added to your `test` gradle task: + +```groovy +test { + // ... + if (hasProperty('headless') || System.getenv('CI')) { + systemProperties = [ + 'java.awt.headless': 'true', + 'testfx.robot' : 'glass', + 'testfx.headless' : 'true', + 'glass.platform' : 'Monocle', + 'monocle.platform' : 'Headless', + 'prism.order' : 'sw', + 'prism.text' : 't2k', + ] + } +} +``` + +Whenever the tests are ran with `CI=true`, headless mode will be enabled allowing for testing in CI environments like GH Actions. + +## Mockito + +Mockito is used to redefine certain methods in the code which currently aren't being tested but could influence the test results, for example by accessing an external API. + +```groovy +testImplementation group: 'org.mockito', name: 'mockito-junit-jupiter', version: mockitoVersion +``` + +--- + +[Overview](README.md) | [Testing Controllers ➡](2-controllers) diff --git a/docs/testing/2-controllers.md b/docs/testing/2-controllers.md new file mode 100644 index 00000000..75229e22 --- /dev/null +++ b/docs/testing/2-controllers.md @@ -0,0 +1,83 @@ +# Testing Controllers + +In the following section, you will learn how to test a basic controller using TestFX and Mockito. + +## ControllerTest + +Testing controllers using TestFX requires the test to extend from `ApplicationTest`. +It is however recommended to create a helper class called something like `ControllerTest` extending `ApplicationTest` instead of extending it directly. +This class will contain some common code to reduce the amount of boilerplate required for each controller test. + +```java +public class ControllerTest extends ApplicationTest { + + @Spy + public final App app = new App(); + @Spy + protected final ResourceBundle resources = ...; // Define common instances here and mock/spy them + + protected Stage stage; // Useful for checking the title for example + + @Override + public void start(Stage stage) throws Exception { + super.start(stage); + this.stage = stage; + app.start(stage); + stage.requestFocus(); // Make the test use the correct stage + } +} +``` + +The main annotations offered by Mockito are `@Spy` and `@Mock`. +Mocking an instance completely removes all default behaviour and content of methods, fields and such, resulting in an empty shell which can later be redefined. +This is useful if the real behaviour isn't needed at all, but the instance itself has to exist. +Spying an instance doesn't touch the default behaviour but allows redefining parts of the logic. + +Spies and Mocks can later be injected into the controller instance which is being tested using `@InjectMocks`. + +## Writing a real test + +Since most of the setup is already defined in the `ControllerTest` class we can just extend it for our own tests. + +```java +@ExtendWith(MockitoExtension.class) +public class SetupControllerTest extends ControllerTest { + + @InjectMocks + SetupController setupController; + + @Override + public void start(Stage stage) throws Exception { + super.start(stage); // It is important to call super.start(stage) to setup the test correctly + app.show(setupController); + } + + @Test + public void test() { + // Since we don't really want to show a different controller, we mock the show() method's behaviour to just return a vbox + doReturn(new VBox()).when(app).show(any(), any()); + + assertEquals("Ludo - Set up the game", app.stage().getTitle()); + + // TestFX offers different methods for interacting with the application + moveTo("2"); + moveBy(0, -20); + press(MouseButton.PRIMARY); + release(MouseButton.PRIMARY); + clickOn("#startButton"); + + // Mockito can be used to check if the show() method was called with certain arguments + verify(app, times(1)).show("ingame", Map.of("playerAmount", 2)); + + } + +} +``` + +Whenever something is loading asynchronously the method `waitForFxEvents()` should be called before checking the results. +This assures that all JavaFX events have been run before continuing the tests. +Another way of waiting is the `sleep()` method, which allows to wait for a predefined time. + +--- + +[⬅ Setup](1-setup.md) | [Overview](README.md) | [Testing SubComponents ➡](2-subcomponents.md) \ No newline at end of file diff --git a/docs/testing/README.md b/docs/testing/README.md new file mode 100644 index 00000000..c1e28f92 --- /dev/null +++ b/docs/testing/README.md @@ -0,0 +1,10 @@ +# Testing + +There are plenty of ways to test different parts of your application. +This section covers the testing of controllers including view tests using TestFX and mocking using Mockito. +Since fulibFx uses Dagger internally and for example applications, the last subsection also contains some hints for working with dagger in tests. + +1. [Setup](1-setup.md) +2. [Testing Controllers](2-controllers.md) +3. [Testing SubComponents](3-subcomponents.md) +4. [Testing with Dagger](4-dagger.md) \ No newline at end of file diff --git a/ludo/src/main/java/de/uniks/ludo/controller/IngameController.java b/ludo/src/main/java/de/uniks/ludo/controller/IngameController.java index 62c84337..7f2a19b9 100644 --- a/ludo/src/main/java/de/uniks/ludo/controller/IngameController.java +++ b/ludo/src/main/java/de/uniks/ludo/controller/IngameController.java @@ -14,6 +14,7 @@ import javafx.scene.effect.BlurType; import javafx.scene.effect.Shadow; import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; @@ -94,12 +95,13 @@ void drawPieces() { @OnRender void setupDice() { - this.diceSubComponent.setOnMouseClicked(event -> rollDice()); + this.diceSubComponent.setOnMouseClicked(event -> rollDice(null)); this.subscriber.bind(this.diceSubComponent.eyesLabel.textFillProperty(), this.currentPlayer.map(player -> Color.web(Constants.COLORS.get(player.getId())))); } @OnKey(code = KeyCode.R) - void rollDice() { + void rollDice(KeyEvent event) { + System.out.println("TEST"); if (!this.diceSubComponent.isEnabled()) return; LudoUtil.playSound(Constants.SOUND_ROLL_DICES); this.subscriber.subscribe(this.diceSubComponent.roll(), Schedulers.computation(), From d241d6d3de73ec13a689fc43f3054e2b0d7d4611 Mon Sep 17 00:00:00 2001 From: LeStegii Date: Wed, 15 May 2024 12:24:15 +0200 Subject: [PATCH 2/7] fix(ludo): Remove unused code --- .../main/java/de/uniks/ludo/controller/IngameController.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ludo/src/main/java/de/uniks/ludo/controller/IngameController.java b/ludo/src/main/java/de/uniks/ludo/controller/IngameController.java index 7f2a19b9..6982357d 100644 --- a/ludo/src/main/java/de/uniks/ludo/controller/IngameController.java +++ b/ludo/src/main/java/de/uniks/ludo/controller/IngameController.java @@ -95,13 +95,12 @@ void drawPieces() { @OnRender void setupDice() { - this.diceSubComponent.setOnMouseClicked(event -> rollDice(null)); + this.diceSubComponent.setOnMouseClicked(event -> rollDice()); this.subscriber.bind(this.diceSubComponent.eyesLabel.textFillProperty(), this.currentPlayer.map(player -> Color.web(Constants.COLORS.get(player.getId())))); } @OnKey(code = KeyCode.R) - void rollDice(KeyEvent event) { - System.out.println("TEST"); + void rollDice() { if (!this.diceSubComponent.isEnabled()) return; LudoUtil.playSound(Constants.SOUND_ROLL_DICES); this.subscriber.subscribe(this.diceSubComponent.roll(), Schedulers.computation(), From ca0b9a7aadfd48cb818c73c19ba1880faa3bf41a Mon Sep 17 00:00:00 2001 From: LeStegii Date: Wed, 15 May 2024 12:26:18 +0200 Subject: [PATCH 3/7] docs(testing): Add docs about testing subcomponents --- docs/testing/3-subcomponents.md | 34 +++++++++++++++++++ ludo/build.gradle | 1 + .../ludo/controller/sub/DiceSubComponent.java | 2 +- .../ludo/controller/IngameControllerTest.java | 4 +-- 4 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 docs/testing/3-subcomponents.md diff --git a/docs/testing/3-subcomponents.md b/docs/testing/3-subcomponents.md new file mode 100644 index 00000000..00263a13 --- /dev/null +++ b/docs/testing/3-subcomponents.md @@ -0,0 +1,34 @@ +# Testing SubComponents + +As subcomponents extend from JavaFX nodes mocking them destroys their functionality making them useless and prevents them from being rendered. +Spying has similar issues. Another problem with subcomponents is that they often require multiple dependencies like services themselves. + +Therefor the best way of testing a subcomponent is by creating a field inside the controller test and annotating it with `@InjectMocks` so that all the dependencies are injected into it as well. +Since fields annotated with `@InjectMocks` cannot be injected into other fields annotated with the same annotation, this has to be done manually. + +```java +@ExtendWith(MockitoExtension.class) +public class IngameControllerTest extends ControllerTest { + + @Spy + GameService gameService; + @InjectMocks + DiceSubComponent diceSubComponent; + // ... + + @InjectMocks + IngameController ingameController; + + @Override + public void start(Stage stage) throws Exception { + super.start(stage); + ingameController.diceSubComponent = diceSubComponent; // Manually set the component instance + app.show(ingameController, Map.of("playerAmount", 2)); + } + + @Test + public void test() { + // ... + } +} +``` \ No newline at end of file diff --git a/ludo/build.gradle b/ludo/build.gradle index 46f592bb..451e040d 100644 --- a/ludo/build.gradle +++ b/ludo/build.gradle @@ -36,6 +36,7 @@ dependencies { testImplementation group: 'org.testfx', name: 'openjfx-monocle', version: monocleVersion testImplementation group: 'org.mockito', name: 'mockito-junit-jupiter', version: mockitoVersion testAnnotationProcessor group: 'com.google.dagger', name: 'dagger-compiler', version: daggerVersion + testImplementation group: 'org.hamcrest', name: 'hamcrest', version: hamcrestVersion } java { diff --git a/ludo/src/main/java/de/uniks/ludo/controller/sub/DiceSubComponent.java b/ludo/src/main/java/de/uniks/ludo/controller/sub/DiceSubComponent.java index 79dab483..d31ca7f2 100644 --- a/ludo/src/main/java/de/uniks/ludo/controller/sub/DiceSubComponent.java +++ b/ludo/src/main/java/de/uniks/ludo/controller/sub/DiceSubComponent.java @@ -22,7 +22,7 @@ public class DiceSubComponent extends VBox { public Label eyesLabel; @Inject - public GameService gameService; + GameService gameService; private final BooleanProperty enabled = new SimpleBooleanProperty(true); diff --git a/ludo/src/test/java/de/uniks/ludo/controller/IngameControllerTest.java b/ludo/src/test/java/de/uniks/ludo/controller/IngameControllerTest.java index 6959a2ec..95741be2 100644 --- a/ludo/src/test/java/de/uniks/ludo/controller/IngameControllerTest.java +++ b/ludo/src/test/java/de/uniks/ludo/controller/IngameControllerTest.java @@ -35,7 +35,7 @@ public class IngameControllerTest extends ControllerTest { GameService gameService; @Spy Subscriber subscriber; - @Spy + @InjectMocks DiceSubComponent diceSubComponent; @InjectMocks @@ -44,7 +44,7 @@ public class IngameControllerTest extends ControllerTest { @Override public void start(Stage stage) throws Exception { super.start(stage); - diceSubComponent.gameService = gameService; + ingameController.diceSubComponent = diceSubComponent; app.show(ingameController, Map.of("playerAmount", 2)); } From fbb20939d30a017f4e9e1b201d3e7cec689b5c17 Mon Sep 17 00:00:00 2001 From: LeStegii Date: Wed, 15 May 2024 12:28:13 +0200 Subject: [PATCH 4/7] docs(testing): Little tweaks and changes --- docs/testing/2-controllers.md | 3 ++- docs/testing/3-subcomponents.md | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/testing/2-controllers.md b/docs/testing/2-controllers.md index 75229e22..bf62757c 100644 --- a/docs/testing/2-controllers.md +++ b/docs/testing/2-controllers.md @@ -38,6 +38,7 @@ Spies and Mocks can later be injected into the controller instance which is bein ## Writing a real test Since most of the setup is already defined in the `ControllerTest` class we can just extend it for our own tests. +In order to get Mockito working, the class has to be annotated with `@ExtendWith(MockitoExtension.class)`. ```java @ExtendWith(MockitoExtension.class) @@ -80,4 +81,4 @@ Another way of waiting is the `sleep()` method, which allows to wait for a prede --- -[⬅ Setup](1-setup.md) | [Overview](README.md) | [Testing SubComponents ➡](2-subcomponents.md) \ No newline at end of file +[⬅ Setup](1-setup.md) | [Overview](README.md) | [Testing SubComponents ➡](3-subcomponents.md) \ No newline at end of file diff --git a/docs/testing/3-subcomponents.md b/docs/testing/3-subcomponents.md index 00263a13..e377caaa 100644 --- a/docs/testing/3-subcomponents.md +++ b/docs/testing/3-subcomponents.md @@ -31,4 +31,7 @@ public class IngameControllerTest extends ControllerTest { // ... } } -``` \ No newline at end of file +``` +--- + +[⬅ Testing Controllers](2-controllers.md) | [Overview](README.md) | [Testing with Dagger ➡](4-dagger.md) \ No newline at end of file From 44df77141632be9949bda06fc9ff8cf5588df7eb Mon Sep 17 00:00:00 2001 From: LeStegii Date: Wed, 15 May 2024 12:36:44 +0200 Subject: [PATCH 5/7] docs(testing): Add section about Dagger --- docs/README.md | 3 +- docs/testing/3-subcomponents.md | 1 + docs/testing/4-dagger.md | 49 +++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 docs/testing/4-dagger.md diff --git a/docs/README.md b/docs/README.md index 62f11a13..5b2ffa18 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,4 +7,5 @@ More detailed information can be found in the categories below. - [Controllers and Components](controller/README.md) - [Tutorial](tutorial/how-to-start.md) -- [Other Features](features/README.md) \ No newline at end of file +- [Other Features](features/README.md) +- [Testing](testing/README.md) \ No newline at end of file diff --git a/docs/testing/3-subcomponents.md b/docs/testing/3-subcomponents.md index e377caaa..728c27f9 100644 --- a/docs/testing/3-subcomponents.md +++ b/docs/testing/3-subcomponents.md @@ -32,6 +32,7 @@ public class IngameControllerTest extends ControllerTest { } } ``` + --- [⬅ Testing Controllers](2-controllers.md) | [Overview](README.md) | [Testing with Dagger ➡](4-dagger.md) \ No newline at end of file diff --git a/docs/testing/4-dagger.md b/docs/testing/4-dagger.md new file mode 100644 index 00000000..e8e0bebb --- /dev/null +++ b/docs/testing/4-dagger.md @@ -0,0 +1,49 @@ +# Testing with Dagger + +When using Dagger inside the application, testing the app requires a testcomponent to be present. +This component contains all the dependencies the main module provides, but modified in a way that doesn't require a connection for example. + +The component itself can just extend the main component and then use modules to override certain dependencies. +Inside the modules Mockito methods such as `spy()` and `mock()` can be used to create the required instances. +If specific behaviour is required, the instances can also be created manually. + +```java +@Component(modules = {MainModule.class, TestModule.class}) +@Singleton +public interface TestComponent extends MainComponent { + + @Component.Builder + interface Builder extends MainComponent.Builder { + TestComponent build(); + } +} +``` + +```java +@Module +public class TestModule { + + @Provides + GameService gameService() { + return new GameService(new Random(42)); + } + +} +``` + +Now that the component and modules exist, we have to create a way of setting the component our app uses. +This step however is dependent on how the application is structured. +The easiest way is to create a setter method and call it, before the app starts. + +```java +@Override +public void start(Stage stage) throws Exception { + super.start(stage); + app.setComponent(DaggerTestComponent.builder().mainApp(app).build()); + app.start(stage); + stage.requestFocus(); +} +``` +--- + +[⬅ Testing SubComponents](3-subcomponents.md) | [Overview](README.md) \ No newline at end of file From 7a5c3a045e9a65980d2cc4474a7a1e1c8be335e0 Mon Sep 17 00:00:00 2001 From: Paul Mertens <50475262+LeStegii@users.noreply.github.com> Date: Thu, 16 May 2024 12:34:01 +0200 Subject: [PATCH 6/7] docs(testing): Tweaks descriptions Co-authored-by: Adrian Kunz --- docs/testing/1-setup.md | 6 +++--- docs/testing/2-controllers.md | 18 +++++++++++++----- docs/testing/3-subcomponents.md | 7 ++++--- docs/testing/4-dagger.md | 2 +- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/docs/testing/1-setup.md b/docs/testing/1-setup.md index ff144750..c1613613 100644 --- a/docs/testing/1-setup.md +++ b/docs/testing/1-setup.md @@ -2,11 +2,11 @@ In order to properly test your application, it is recommended to use [TestFX](https://github.com/TestFX/TestFX) alongside [Mockito](https://github.com/mockito/mockito). For a full explanation of both libraries, checkout their official documentation, as the following documentation will only cover a small part of what the projects have to offer. -## TestFX +## TestFX TestFX can be used to test the frontend of your application by checking if certain requirements are met, for example view elements being visible or having a certain property. -Alongside TestFX, we also include Monocle which allows for headless testing without the app having to be open on your screen every time the tests are ran. +Alongside TestFX, we also include Monocle which allows for headless testing without the app having to be open on your screen every time the tests run. ```groovy testImplementation group: 'org.testfx', name: 'testfx-junit5', version: testFxVersion @@ -32,7 +32,7 @@ test { } ``` -Whenever the tests are ran with `CI=true`, headless mode will be enabled allowing for testing in CI environments like GH Actions. +Whenever the tests are ran with `CI=true`, headless mode will be enabled allowing for testing in CI environments like GitHub Actions. ## Mockito diff --git a/docs/testing/2-controllers.md b/docs/testing/2-controllers.md index bf62757c..86ea2fb4 100644 --- a/docs/testing/2-controllers.md +++ b/docs/testing/2-controllers.md @@ -5,16 +5,16 @@ In the following section, you will learn how to test a basic controller using Te ## ControllerTest Testing controllers using TestFX requires the test to extend from `ApplicationTest`. -It is however recommended to create a helper class called something like `ControllerTest` extending `ApplicationTest` instead of extending it directly. +It is however recommended to create a helper class like `ControllerTest` extending `ApplicationTest`. This class will contain some common code to reduce the amount of boilerplate required for each controller test. ```java public class ControllerTest extends ApplicationTest { @Spy - public final App app = new App(); + protected App app = new App(); @Spy - protected final ResourceBundle resources = ...; // Define common instances here and mock/spy them + protected ResourceBundle resources = ...; // Define common instances here and mock/spy them protected Stage stage; // Useful for checking the title for example @@ -25,13 +25,21 @@ public class ControllerTest extends ApplicationTest { app.start(stage); stage.requestFocus(); // Make the test use the correct stage } + + @Override + public void stop() throws Exception { + super.stop(); + app.stop(); + app = null; + stage = null; + } } ``` The main annotations offered by Mockito are `@Spy` and `@Mock`. Mocking an instance completely removes all default behaviour and content of methods, fields and such, resulting in an empty shell which can later be redefined. This is useful if the real behaviour isn't needed at all, but the instance itself has to exist. -Spying an instance doesn't touch the default behaviour but allows redefining parts of the logic. +Spying an instance doesn't touch the default behaviour but allows redefining parts of the logic and checking whether methods have been called using `verify`. Spies and Mocks can later be injected into the controller instance which is being tested using `@InjectMocks`. @@ -76,7 +84,7 @@ public class SetupControllerTest extends ControllerTest { ``` Whenever something is loading asynchronously the method `waitForFxEvents()` should be called before checking the results. -This assures that all JavaFX events have been run before continuing the tests. +This ensures that all JavaFX events have been run before continuing the tests. Another way of waiting is the `sleep()` method, which allows to wait for a predefined time. --- diff --git a/docs/testing/3-subcomponents.md b/docs/testing/3-subcomponents.md index 728c27f9..14a1b8a1 100644 --- a/docs/testing/3-subcomponents.md +++ b/docs/testing/3-subcomponents.md @@ -1,9 +1,10 @@ # Testing SubComponents -As subcomponents extend from JavaFX nodes mocking them destroys their functionality making them useless and prevents them from being rendered. -Spying has similar issues. Another problem with subcomponents is that they often require multiple dependencies like services themselves. +As subcomponents extend from JavaFX nodes, mocking them destroys their functionality, which prevents them from being rendered and makes them useless. +Spying has similar issues. +Another problem with subcomponents is that they often require multiple dependencies like services themselves. -Therefor the best way of testing a subcomponent is by creating a field inside the controller test and annotating it with `@InjectMocks` so that all the dependencies are injected into it as well. +Therefore the best way of testing a subcomponent is by creating a field inside the controller test and annotating it with `@InjectMocks` so that all the dependencies are injected into it as well. Since fields annotated with `@InjectMocks` cannot be injected into other fields annotated with the same annotation, this has to be done manually. ```java diff --git a/docs/testing/4-dagger.md b/docs/testing/4-dagger.md index e8e0bebb..a33180a8 100644 --- a/docs/testing/4-dagger.md +++ b/docs/testing/4-dagger.md @@ -4,7 +4,7 @@ When using Dagger inside the application, testing the app requires a testcompone This component contains all the dependencies the main module provides, but modified in a way that doesn't require a connection for example. The component itself can just extend the main component and then use modules to override certain dependencies. -Inside the modules Mockito methods such as `spy()` and `mock()` can be used to create the required instances. +Inside the modules, Mockito methods such as `spy()` and `mock()` can be used to create the required instances. If specific behaviour is required, the instances can also be created manually. ```java From 1e70e9d431fb68518827af6cf75cec0d75bbec1d Mon Sep 17 00:00:00 2001 From: LeStegii Date: Thu, 16 May 2024 12:46:17 +0200 Subject: [PATCH 7/7] docs(testing): Apply suggested changes --- docs/testing/2-controllers.md | 9 ++++++--- docs/testing/4-dagger.md | 25 ++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/docs/testing/2-controllers.md b/docs/testing/2-controllers.md index 86ea2fb4..33a9fc62 100644 --- a/docs/testing/2-controllers.md +++ b/docs/testing/2-controllers.md @@ -10,7 +10,7 @@ This class will contain some common code to reduce the amount of boilerplate req ```java public class ControllerTest extends ApplicationTest { - + @Spy protected App app = new App(); @Spy @@ -63,8 +63,8 @@ public class SetupControllerTest extends ControllerTest { @Test public void test() { - // Since we don't really want to show a different controller, we mock the show() method's behaviour to just return a vbox - doReturn(new VBox()).when(app).show(any(), any()); + // Since we don't really want to show a different controller, we mock the show() method's behaviour to just return null + doReturn(null).when(app).show(any(), any()); assertEquals("Ludo - Set up the game", app.stage().getTitle()); @@ -74,6 +74,8 @@ public class SetupControllerTest extends ControllerTest { press(MouseButton.PRIMARY); release(MouseButton.PRIMARY); clickOn("#startButton"); + + waitForFxEvents(); // Wait for the logic to run // Mockito can be used to check if the show() method was called with certain arguments verify(app, times(1)).show("ingame", Map.of("playerAmount", 2)); @@ -86,6 +88,7 @@ public class SetupControllerTest extends ControllerTest { Whenever something is loading asynchronously the method `waitForFxEvents()` should be called before checking the results. This ensures that all JavaFX events have been run before continuing the tests. Another way of waiting is the `sleep()` method, which allows to wait for a predefined time. +This is not recommended though as the defined time is either too long or too short and therefore can cause issues or unnecessary delays. --- diff --git a/docs/testing/4-dagger.md b/docs/testing/4-dagger.md index a33180a8..95a4715c 100644 --- a/docs/testing/4-dagger.md +++ b/docs/testing/4-dagger.md @@ -36,14 +36,37 @@ This step however is dependent on how the application is structured. The easiest way is to create a setter method and call it, before the app starts. ```java +// ... +protected TestComponent testComponent; + @Override public void start(Stage stage) throws Exception { super.start(stage); - app.setComponent(DaggerTestComponent.builder().mainApp(app).build()); + this.testComponent = (TestComponent) DaggerTestComponent.builder().mainApp(app).build(); + app.setComponent(testComponent); app.start(stage); stage.requestFocus(); } + +// ... ``` + +The component instance makes it possible to inject services from test classes e.g. AppTest to redefine their behavior. + +```java +public class AppTest extends ControllerTest { + // ... + + @BeforeEach + void setup() { + final AuthApiService authApiService = testComponent.authApiService(); + // ... + } + + // ... +} +``` + --- [⬅ Testing SubComponents](3-subcomponents.md) | [Overview](README.md) \ No newline at end of file