-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* docs(testing): Add main section and subsections for controllers and setup * fix(ludo): Remove unused code * docs(testing): Add docs about testing subcomponents * docs(testing): Little tweaks and changes * docs(testing): Add section about Dagger * docs(testing): Tweaks descriptions * docs(testing): Apply suggested changes --------- Co-authored-by: Adrian Kunz <[email protected]>
- Loading branch information
Showing
10 changed files
with
270 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 run. | ||
|
||
```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 GitHub 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
# 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 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 | ||
protected App app = new App(); | ||
@Spy | ||
protected 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 | ||
} | ||
|
||
@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 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`. | ||
|
||
## 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) | ||
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 null | ||
doReturn(null).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"); | ||
|
||
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)); | ||
|
||
} | ||
|
||
} | ||
``` | ||
|
||
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. | ||
|
||
--- | ||
|
||
[⬅ Setup](1-setup.md) | [Overview](README.md) | [Testing SubComponents ➡](3-subcomponents.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Testing SubComponents | ||
|
||
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. | ||
|
||
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 | ||
@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() { | ||
// ... | ||
} | ||
} | ||
``` | ||
|
||
--- | ||
|
||
[⬅ Testing Controllers](2-controllers.md) | [Overview](README.md) | [Testing with Dagger ➡](4-dagger.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# 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 | ||
// ... | ||
protected TestComponent testComponent; | ||
|
||
@Override | ||
public void start(Stage stage) throws Exception { | ||
super.start(stage); | ||
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters