如何模拟正在使用 TestFX 测试的控制器中的方法?
How to mock methods in a controller that's being tested with TestFX?
这是 TestFX 测试的片段,直接从他们的 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!"));
}
我的问题是,特别是改变 scenes/roots 的操作。正在测试的 controller/scene 的方面在末尾更改了根,这会产生以下堆栈跟踪:
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
我的解决方案是,如您所见,我为 App
中的静态方法创建了一个服务包装器 class(例如 setRoot
导致NPE),如果我可以访问控制器,理论上我可以使用 Mockito 进行模拟。不幸的是,如果你回到上面的代码示例,似乎没有任何访问控制器的概念 class。您可以进行表面级别的创建和与舞台的交互,但我无法弄清楚如何访问底层控制器。当然,我需要访问物理控制器才能模拟其服务 class。
有谁知道我如何获得那个 class 的访问权限,以便我可以将其包装器 class 设置为模拟版本?
如果有人想实际使用它,我可以提供源代码。
想通了。
javafx.fxml.FXMLLoader
有一个名为 getController
的方法。但这并不是那么简单,因为 javafx.fxml.FXMLLoader
必须物理 load
到一个 javafx.scene.Parent
对象中才能使控制器存在。
无论如何,这是一个简短的 bootstrap 设置,您可以遵循。
@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();
}
}
从那里,你可以对控制器做任何你想做的事。 (在我的例子中,嘲笑它)
这是 TestFX 测试的片段,直接从他们的 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!"));
}
我的问题是,特别是改变 scenes/roots 的操作。正在测试的 controller/scene 的方面在末尾更改了根,这会产生以下堆栈跟踪:
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
我的解决方案是,如您所见,我为 App
中的静态方法创建了一个服务包装器 class(例如 setRoot
导致NPE),如果我可以访问控制器,理论上我可以使用 Mockito 进行模拟。不幸的是,如果你回到上面的代码示例,似乎没有任何访问控制器的概念 class。您可以进行表面级别的创建和与舞台的交互,但我无法弄清楚如何访问底层控制器。当然,我需要访问物理控制器才能模拟其服务 class。
有谁知道我如何获得那个 class 的访问权限,以便我可以将其包装器 class 设置为模拟版本?
如果有人想实际使用它,我可以提供源代码。
想通了。
javafx.fxml.FXMLLoader
有一个名为 getController
的方法。但这并不是那么简单,因为 javafx.fxml.FXMLLoader
必须物理 load
到一个 javafx.scene.Parent
对象中才能使控制器存在。
无论如何,这是一个简短的 bootstrap 设置,您可以遵循。
@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();
}
}
从那里,你可以对控制器做任何你想做的事。 (在我的例子中,嘲笑它)