Here is a snippet of what a test looks like for TestFX, pulled directly from their GitHub README:
@ExtendWith(ApplicationExtension.class)
class ClickableButtonTest_JUnit5Hamcrest {
private Button button;
/**
* Will be called with {@code @Before} semantics, i. e. before each test method.
*
* @param stage - Will be injected by the test runner.
*/
@Start
private void start(Stage stage) {
button = new Button("click me!");
button.setId("myButton");
button.setOnAction(actionEvent -> button.setText("clicked!"));
stage.setScene(new Scene(new StackPane(button), 100, 100));
stage.show();
}
/**
* @param robot - Will be injected by the test runner.
*/
@Test
void should_contain_button_with_text(FxRobot robot) {
FxAssert.verifyThat(button, LabeledMatchers.hasText("click me!"));
// or (lookup by css id):
FxAssert.verifyThat("#myButton", LabeledMatchers.hasText("click me!"));
// or (lookup by css class):
FxAssert.verifyThat(".button", LabeledMatchers.hasText("click me!"));
}
My issue is, specifically with actions that change scenes/roots. The aspect of the controller/scene that is being tested, changes the root at the end, which produces the following stack trace:
Caused by: java.lang.NullPointerException
at org.example/org.example.App.setRoot(App.java:67)
at org.example/org.example.services.AppService.setRoot(AppService.java:20)
at org.example/org.example.controllers.SecondaryController.switchToGameScreen(SecondaryController.java:64)
... 57 more
My solution for that, was, as you might be able to discern, that I created a service wrapper class for the static methods in App
(such as setRoot
causing the NPE), which, if I had access to the controller, I could theoretically mock with Mockito. Unfortunately, if you go back to the code example above, there doesn't seem to be any notion of access to the controller class. You get surface level creation and interaction with the stage, but the underlying controller I can't figure out how to access. Of course I need to access the physical controller in order to mock its service class.
Does anyone know how I might gain access to that class, so that I can set its wrapper class to a mocked version?
I can provide the source code if anyone wants to actually play around with it.
Figured it out.
javafx.fxml.FXMLLoader
has a method called getController
. But it's not that simple, because the javafx.fxml.FXMLLoader
has to physically load
into a javafx.scene.Parent
object for the controller to exist.
Anyway, here's a short bootstrap setup you can follow.
@ExtendWith(ApplicationExtension.class)
public class ToTest {
private Controller controller;
@Start
public void setUp(Stage stage) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("totest.fxml"));
Parent root = loader.load();
//This must happen AFTER loader.load()
this.controller = loader.getController();
stage.setScene(new Scene(root, 0, 0));
stage.show();
}
}
From there, you can do whatever you want to the controller. (in my case, mocking it)